]> git.proxmox.com Git - mirror_ifupdown2.git/blame - ifupdown2/addons/vxlan.py
addons: vxlan: if device exists check that it's a vxlan (link_kind)
[mirror_ifupdown2.git] / ifupdown2 / addons / vxlan.py
CommitLineData
d486dd0d
JF
1#!/usr/bin/python
2#
3# Copyright 2014-2017 Cumulus Networks, Inc. All rights reserved.
4# Author: Roopa Prabhu, roopa@cumulusnetworks.com
5#
6
7
8from sets import Set
9from ipaddr import IPNetwork, IPv4Address, IPv4Network, AddressValueError
10
11try:
12 import ifupdown2.ifupdown.policymanager as policymanager
3fb83a7a 13 import ifupdown2.ifupdown.ifupdownflags as ifupdownflags
d486dd0d
JF
14
15 from ifupdown2.nlmanager.nlmanager import Link
16
17 from ifupdown2.ifupdown.iface import *
18 from ifupdown2.ifupdown.utils import utils
19 from ifupdown2.ifupdown.netlink import netlink
20 from ifupdown2.ifupdownaddons.cache import *
21 from ifupdown2.ifupdownaddons.LinkUtils import LinkUtils
22 from ifupdown2.ifupdownaddons.modulebase import moduleBase
23except ImportError:
24 import ifupdown.policymanager as policymanager
3fb83a7a 25 import ifupdown.ifupdownflags as ifupdownflags
d486dd0d
JF
26
27 from nlmanager.nlmanager import Link
28
29 from ifupdown.iface import *
30 from ifupdown.utils import utils
31 from ifupdown.netlink import netlink
32
33 from ifupdownaddons.cache import *
34 from ifupdownaddons.LinkUtils import LinkUtils
35 from ifupdownaddons.modulebase import moduleBase
36
37
38class vxlan(moduleBase):
39 _modinfo = {'mhelp' : 'vxlan module configures vxlan interfaces.',
40 'attrs' : {
41 'vxlan-id' :
42 {'help' : 'vxlan id',
43 'validrange' : ['1', '16777214'],
44 'required' : True,
45 'example': ['vxlan-id 100']},
46 'vxlan-local-tunnelip' :
47 {'help' : 'vxlan local tunnel ip',
48 'validvals' : ['<ipv4>'],
49 'example': ['vxlan-local-tunnelip 172.16.20.103']},
50 'vxlan-svcnodeip' :
51 {'help' : 'vxlan id',
52 'validvals' : ['<ipv4>'],
53 'example': ['vxlan-svcnodeip 172.16.22.125']},
54 'vxlan-remoteip' :
55 {'help' : 'vxlan remote ip',
56 'validvals' : ['<ipv4>'],
57 'example': ['vxlan-remoteip 172.16.22.127'],
58 'multiline': True},
59 'vxlan-learning' :
60 {'help' : 'vxlan learning yes/no',
61 'validvals' : ['yes', 'no', 'on', 'off'],
62 'example': ['vxlan-learning no'],
63 'default': 'yes'},
64 'vxlan-ageing' :
65 {'help' : 'vxlan aging timer',
66 'validrange' : ['0', '4096'],
67 'example': ['vxlan-ageing 300'],
68 'default': '300'},
69 'vxlan-purge-remotes' :
70 {'help' : 'vxlan purge existing remote entries',
71 'validvals' : ['yes', 'no'],
72 'example': ['vxlan-purge-remotes yes'],},
73 'vxlan-port': {
74 'help': 'vxlan UDP port (transmitted to vxlan driver)',
0d6edae7 75 'example': ['vxlan-port 4789'],
d486dd0d
JF
76 'validrange': ['1', '65536'],
77 'default': '4789',
a382b488
JF
78 },
79 'vxlan-physdev':
80 {'help': 'vxlan physical device',
81 'example': ['vxlan-physdev eth1']},
82
d486dd0d
JF
83 }}
84 _clagd_vxlan_anycast_ip = ""
85 _vxlan_local_tunnelip = None
86
87 def __init__(self, *args, **kargs):
88 moduleBase.__init__(self, *args, **kargs)
89 self.ipcmd = None
90 purge_remotes = policymanager.policymanager_api.get_module_globals(module_name=self.__class__.__name__, attr='vxlan-purge-remotes')
91 if purge_remotes:
92 self._purge_remotes = utils.get_boolean_from_string(purge_remotes)
93 else:
94 self._purge_remotes = False
95
96 def syntax_check(self, ifaceobj, ifaceobj_getfunc):
97 if self._is_vxlan_device(ifaceobj):
98 if not ifaceobj.get_attr_value_first('vxlan-local-tunnelip') and not vxlan._vxlan_local_tunnelip:
99 self.logger.warning('%s: missing vxlan-local-tunnelip' % ifaceobj.name)
100 return False
101 return self.syntax_check_localip_anycastip_equal(
102 ifaceobj.name,
103 ifaceobj.get_attr_value_first('vxlan-local-tunnelip') or vxlan._vxlan_local_tunnelip,
104 vxlan._clagd_vxlan_anycast_ip
105 )
106 return True
107
108 def syntax_check_localip_anycastip_equal(self, ifname, local_ip, anycast_ip):
109 try:
110 if IPNetwork(local_ip) == IPNetwork(anycast_ip):
111 self.logger.warning('%s: vxlan-local-tunnelip and clagd-vxlan-anycast-ip are identical (%s)'
112 % (ifname, local_ip))
113 return False
114 except:
115 pass
116 return True
117
118 def get_dependent_ifacenames(self, ifaceobj, ifaceobjs_all=None):
119 if self._is_vxlan_device(ifaceobj):
120 ifaceobj.link_kind |= ifaceLinkKind.VXLAN
121 self._set_global_local_ip(ifaceobj)
122 elif ifaceobj.name == 'lo':
123 clagd_vxlan_list = ifaceobj.get_attr_value('clagd-vxlan-anycast-ip')
124 if clagd_vxlan_list:
125 if len(clagd_vxlan_list) != 1:
126 self.log_warn('%s: multiple clagd-vxlan-anycast-ip lines, using first one'
127 % (ifaceobj.name,))
128 vxlan._clagd_vxlan_anycast_ip = clagd_vxlan_list[0]
129
130 self._set_global_local_ip(ifaceobj)
a382b488
JF
131
132 # If we should use a specific underlay device for the VXLAN
133 # tunnel make sure this device is set up before the VXLAN iface.
134 physdev = ifaceobj.get_attr_value_first('vxlan-physdev')
135
136 if physdev:
137 return [physdev]
138
d486dd0d
JF
139 return None
140
141 def _set_global_local_ip(self, ifaceobj):
142 vxlan_local_tunnel_ip = ifaceobj.get_attr_value_first('vxlan-local-tunnelip')
143 if vxlan_local_tunnel_ip and not vxlan._vxlan_local_tunnelip:
144 vxlan._vxlan_local_tunnelip = vxlan_local_tunnel_ip
145
146 def _is_vxlan_device(self, ifaceobj):
147 if ifaceobj.get_attr_value_first('vxlan-id'):
148 return True
149 return False
150
151 def _get_purge_remotes(self, ifaceobj):
152 if not ifaceobj:
153 return self._purge_remotes
154 purge_remotes = ifaceobj.get_attr_value_first('vxlan-purge-remotes')
155 if purge_remotes:
156 purge_remotes = utils.get_boolean_from_string(purge_remotes)
157 else:
158 purge_remotes = self._purge_remotes
159 return purge_remotes
160
161 def should_create_set_vxlan(self, link_exists, ifname, vxlan_id, local, learning, ageing, group):
162 """
163 should we issue a netlink: ip link add dev %ifname type vxlan ...?
164 checking each attribute against the cache
165 """
166 if not link_exists:
167 return True
168
169 try:
170 if ageing:
171 ageing = int(ageing)
172 except:
173 pass
174
175 for attr_list, value in (
176 ((ifname, 'linkinfo', Link.IFLA_VXLAN_ID), vxlan_id),
177 ((ifname, 'linkinfo', Link.IFLA_VXLAN_AGEING), ageing),
178 ((ifname, 'linkinfo', Link.IFLA_VXLAN_LOCAL), local),
179 ((ifname, 'linkinfo', Link.IFLA_VXLAN_LEARNING), learning),
180 ((ifname, 'linkinfo', Link.IFLA_VXLAN_GROUP), group),
181 ):
182 if value and not self.ipcmd.cache_check(attr_list, value):
183 return True
184 return False
185
186 def _vxlan_create(self, ifaceobj):
187 vxlanid = ifaceobj.get_attr_value_first('vxlan-id')
188 if vxlanid:
189 ifname = ifaceobj.name
190 anycastip = self._clagd_vxlan_anycast_ip
191 group = ifaceobj.get_attr_value_first('vxlan-svcnodeip')
192
193 local = ifaceobj.get_attr_value_first('vxlan-local-tunnelip')
194 if not local and vxlan._vxlan_local_tunnelip:
195 local = vxlan._vxlan_local_tunnelip
196
197 self.syntax_check_localip_anycastip_equal(ifname, local, anycastip)
198 # if both local-ip and anycast-ip are identical the function prints a warning
199
200 ageing = ifaceobj.get_attr_value_first('vxlan-ageing')
201 vxlan_port = ifaceobj.get_attr_value_first('vxlan-port')
a382b488 202 physdev = ifaceobj.get_attr_value_first('vxlan-physdev')
d486dd0d
JF
203 purge_remotes = self._get_purge_remotes(ifaceobj)
204
205 link_exists = self.ipcmd.link_exists(ifname)
206
207 if (not link_exists or
208 not ifaceobj.link_privflags & ifaceLinkPrivFlags.BRIDGE_PORT):
209 vxlan_learning = ifaceobj.get_attr_value_first('vxlan-learning')
210 if not vxlan_learning:
211 vxlan_learning = self.get_attr_default_value('vxlan-learning')
212 learning = utils.get_boolean_from_string(vxlan_learning)
213 else:
214 learning = utils.get_boolean_from_string(
215 self.ipcmd.get_vxlandev_learning(ifname))
216
217 if link_exists:
218 vxlanattrs = self.ipcmd.get_vxlandev_attrs(ifname)
219 # on ifreload do not overwrite anycast_ip to individual ip
220 # if clagd has modified
221 if vxlanattrs:
222 running_localtunnelip = vxlanattrs.get('local')
223 if (anycastip and running_localtunnelip and
224 anycastip == running_localtunnelip):
225 local = running_localtunnelip
226 if vxlanattrs.get('vxlanid') != vxlanid:
227 self.log_error('%s: Cannot change running vxlan id: '
228 'Operation not supported' % ifname, ifaceobj)
b067bba9
JF
229 else:
230 device_link_kind = self.ipcmd.link_get_kind(ifname)
231 if not device_link_kind:
232 self.logger.error("%s: device already exists and is not a vxlan" % ifname)
233 ifaceobj.set_status(ifaceStatus.ERROR)
234 return
235 elif device_link_kind != "vxlan":
236 self.logger.error("%s: device already exists and is not a vxlan (type %s)" % (ifname, device_link_kind))
237 ifaceobj.set_status(ifaceStatus.ERROR)
238 return
239
d486dd0d
JF
240 try:
241 vxlanid = int(vxlanid)
242 except:
243 self.log_error('%s: invalid vxlan-id \'%s\'' % (ifname, vxlanid), ifaceobj)
244
245 if not group:
246 group = policymanager.policymanager_api.get_attr_default(
247 module_name=self.__class__.__name__,
248 attr='vxlan-svcnodeip'
249 )
250
251 if group:
252 try:
253 group = IPv4Address(group)
254 except AddressValueError:
255 try:
256 group_ip = IPv4Network(group).ip
257 self.logger.warning('%s: vxlan-svcnodeip %s: netmask ignored' % (ifname, group))
258 group = group_ip
259 except:
260 raise Exception('%s: invalid vxlan-svcnodeip %s: must be in ipv4 format' % (ifname, group))
261
262 if not local:
263 local = policymanager.policymanager_api.get_attr_default(
264 module_name=self.__class__.__name__,
265 attr='vxlan-local-tunnelip'
266 )
267
268 if local:
269 try:
270 local = IPv4Address(local)
271 except AddressValueError:
272 try:
273 local_ip = IPv4Network(local).ip
274 self.logger.warning('%s: vxlan-local-tunnelip %s: netmask ignored' % (ifname, local))
275 local = local_ip
276 except:
277 raise Exception('%s: invalid vxlan-local-tunnelip %s: must be in ipv4 format' % (ifname, local))
278
279 if not ageing:
280 ageing = policymanager.policymanager_api.get_attr_default(
281 module_name=self.__class__.__name__,
282 attr='vxlan-ageing'
283 )
284
285 if not ageing and link_exists:
286 # if link doesn't exist we let the kernel define ageing
287 ageing = self.get_attr_default_value('vxlan-ageing')
288
289 if not vxlan_port:
290 vxlan_port = policymanager.policymanager_api.get_attr_default(
291 module_name=self.__class__.__name__,
292 attr='vxlan-port'
293 )
294
295 try:
296 vxlan_port = int(vxlan_port)
297 except TypeError:
298 # TypeError means vxlan_port was None
299 # ie: not provided by the user or the policy
300 vxlan_port = netlink.VXLAN_UDP_PORT
301 except ValueError as e:
302 self.logger.warning('%s: vxlan-port: using default %s: invalid configured value %s' % (ifname, netlink.VXLAN_UDP_PORT, str(e)))
303 vxlan_port = netlink.VXLAN_UDP_PORT
304
b067bba9 305 if link_exists and vxlanattrs and not ifupdownflags.flags.DRYRUN:
d486dd0d
JF
306 cache_port = vxlanattrs.get(Link.IFLA_VXLAN_PORT)
307 if vxlan_port != cache_port:
308 self.logger.warning('%s: vxlan-port (%s) cannot be changed - to apply the desired change please run: ifdown %s && ifup %s'
309 % (ifname, cache_port, ifname, ifname))
310 vxlan_port = cache_port
311
312 if self.should_create_set_vxlan(link_exists, ifname, vxlanid, local, learning, ageing, group):
313 try:
314 netlink.link_add_vxlan(ifname, vxlanid,
315 local=local,
316 learning=learning,
317 ageing=ageing,
318 group=group,
a382b488
JF
319 dstport=vxlan_port,
320 physdev=physdev)
d486dd0d
JF
321 except Exception as e_netlink:
322 self.logger.debug('%s: vxlan netlink: %s' % (ifname, str(e_netlink)))
323 try:
324 self.ipcmd.link_create_vxlan(ifname, vxlanid,
325 localtunnelip=local,
326 svcnodeip=group,
327 remoteips=ifaceobj.get_attr_value('vxlan-remoteip'),
328 learning='on' if learning else 'off',
329 ageing=ageing)
330 except Exception as e_iproute2:
331 self.logger.warning('%s: vxlan add/set failed: %s' % (ifname, str(e_iproute2)))
332 return
333
334 try:
335 # manually adding an entry to the caching after creating/updating the vxlan
336 if not ifname in linkCache.links:
337 linkCache.links[ifname] = {'linkinfo': {}}
338 linkCache.links[ifname]['linkinfo'].update({
339 'learning': learning,
340 Link.IFLA_VXLAN_LEARNING: learning,
341 'vxlanid': str(vxlanid),
342 Link.IFLA_VXLAN_ID: vxlanid
343 })
344 if ageing:
345 linkCache.links[ifname]['linkinfo'].update({
346 'ageing': ageing,
347 Link.IFLA_VXLAN_AGEING: int(ageing)
348 })
349 except:
350 pass
351 else:
352 self.logger.info('%s: vxlan already exists' % ifname)
353 # if the vxlan already exists it's already cached
354
355 remoteips = ifaceobj.get_attr_value('vxlan-remoteip')
356 if remoteips:
357 try:
358 for remoteip in remoteips:
359 IPv4Address(remoteip)
360 except Exception as e:
361 self.log_error('%s: vxlan-remoteip: %s' %(ifaceobj.name, str(e)))
362
363 if purge_remotes or remoteips:
364 # figure out the diff for remotes and do the bridge fdb updates
365 # only if provisioned by user and not by an vxlan external
366 # controller.
367 peers = self.ipcmd.get_vxlan_peers(ifaceobj.name, group)
368 if local and remoteips and local in remoteips:
369 remoteips.remove(local)
370 cur_peers = set(peers)
371 if remoteips:
372 new_peers = set(remoteips)
373 del_list = cur_peers.difference(new_peers)
374 add_list = new_peers.difference(cur_peers)
375 else:
376 del_list = cur_peers
377 add_list = []
378
379 for addr in del_list:
380 try:
381 self.ipcmd.bridge_fdb_del(ifaceobj.name,
382 '00:00:00:00:00:00',
383 None, True, addr)
384 except:
385 pass
386
387 for addr in add_list:
388 try:
389 self.ipcmd.bridge_fdb_append(ifaceobj.name,
390 '00:00:00:00:00:00',
391 None, True, addr)
392 except:
393 pass
394
395 def _up(self, ifaceobj):
396 self._vxlan_create(ifaceobj)
397
398 def _down(self, ifaceobj):
399 try:
400 self.ipcmd.link_delete(ifaceobj.name)
401 except Exception, e:
402 self.log_warn(str(e))
403
404 def _query_check_n_update(self, ifaceobj, ifaceobjcurr, attrname, attrval,
405 running_attrval):
406 if not ifaceobj.get_attr_value_first(attrname):
407 return
408 if running_attrval and attrval == running_attrval:
409 ifaceobjcurr.update_config_with_status(attrname, attrval, 0)
410 else:
411 ifaceobjcurr.update_config_with_status(attrname, running_attrval, 1)
412
413 def _query_check_n_update_addresses(self, ifaceobjcurr, attrname,
414 addresses, running_addresses):
415 if addresses:
416 for a in addresses:
417 if a in running_addresses:
418 ifaceobjcurr.update_config_with_status(attrname, a, 0)
419 else:
420 ifaceobjcurr.update_config_with_status(attrname, a, 1)
421 running_addresses = Set(running_addresses).difference(
422 Set(addresses))
423 [ifaceobjcurr.update_config_with_status(attrname, a, 1)
424 for a in running_addresses]
425
426 def _query_check(self, ifaceobj, ifaceobjcurr):
427 if not self.ipcmd.link_exists(ifaceobj.name):
428 return
429 # Update vxlan object
430 vxlanattrs = self.ipcmd.get_vxlandev_attrs(ifaceobj.name)
431 if not vxlanattrs:
432 ifaceobjcurr.check_n_update_config_with_status_many(ifaceobj,
433 self.get_mod_attrs(), -1)
434 return
435 self._query_check_n_update(ifaceobj, ifaceobjcurr, 'vxlan-id',
436 ifaceobj.get_attr_value_first('vxlan-id'),
437 vxlanattrs.get('vxlanid'))
438
439 self._query_check_n_update(
440 ifaceobj,
441 ifaceobjcurr,
442 'vxlan-port',
443 ifaceobj.get_attr_value_first('vxlan-port'),
444 str(vxlanattrs.get(Link.IFLA_VXLAN_PORT))
445 )
446
447 running_attrval = vxlanattrs.get('local')
448 attrval = ifaceobj.get_attr_value_first('vxlan-local-tunnelip')
449 if not attrval:
450 attrval = vxlan._vxlan_local_tunnelip
451 ifaceobj.update_config('vxlan-local-tunnelip', attrval)
452
453 if running_attrval == self._clagd_vxlan_anycast_ip:
454 # if local ip is anycast_ip, then let query_check to go through
455 attrval = self._clagd_vxlan_anycast_ip
456 self._query_check_n_update(ifaceobj, ifaceobjcurr, 'vxlan-local-tunnelip',
457 attrval, running_attrval)
458
459 self._query_check_n_update(ifaceobj, ifaceobjcurr, 'vxlan-svcnodeip',
460 ifaceobj.get_attr_value_first('vxlan-svcnodeip'),
461 vxlanattrs.get('svcnode'))
462
463 purge_remotes = self._get_purge_remotes(ifaceobj)
464 if purge_remotes or ifaceobj.get_attr_value('vxlan-remoteip'):
465 # If purge remotes or if vxlan-remoteip's are set
466 # in the config file, we are owners of the installed
467 # remote-ip's, lets check and report any remote ips we don't
468 # understand
469 self._query_check_n_update_addresses(ifaceobjcurr, 'vxlan-remoteip',
470 ifaceobj.get_attr_value('vxlan-remoteip'),
471 self.ipcmd.get_vxlan_peers(ifaceobj.name, vxlanattrs.get('svcnode')))
472
473 learning = ifaceobj.get_attr_value_first('vxlan-learning')
474 if learning:
475 running_learning = vxlanattrs.get('learning')
476 if learning == 'yes' and running_learning == 'on':
477 running_learning = 'yes'
478 elif learning == 'no' and running_learning == 'off':
479 running_learning = 'no'
480 if learning == running_learning:
481 ifaceobjcurr.update_config_with_status('vxlan-learning',
482 running_learning, 0)
483 else:
484 ifaceobjcurr.update_config_with_status('vxlan-learning',
485 running_learning, 1)
486 ageing = ifaceobj.get_attr_value_first('vxlan-ageing')
487 if not ageing:
488 ageing = self.get_mod_subattr('vxlan-ageing', 'default')
489 self._query_check_n_update(ifaceobj, ifaceobjcurr, 'vxlan-ageing',
490 ageing, vxlanattrs.get('ageing'))
491
a382b488
JF
492 physdev = ifaceobj.get_attr_value_first('vxlan-physdev')
493
494 if physdev:
495 ifla_vxlan_link = vxlanattrs.get(Link.IFLA_VXLAN_LINK)
496
497 if ifla_vxlan_link:
498 self._query_check_n_update(
499 ifaceobj,
500 ifaceobjcurr,
501 'vxlan-physdev',
502 physdev,
503 netlink.get_iface_name(ifla_vxlan_link)
504 )
505 else:
506 ifaceobjcurr.update_config_with_status('vxlan-physdev', physdev, 1)
507
508
d486dd0d
JF
509 def _query_running(self, ifaceobjrunning):
510 vxlanattrs = self.ipcmd.get_vxlandev_attrs(ifaceobjrunning.name)
511 if not vxlanattrs:
512 return
513 attrval = vxlanattrs.get('vxlanid')
514 if attrval:
515 ifaceobjrunning.update_config('vxlan-id', vxlanattrs.get('vxlanid'))
516 else:
517 # if there is no vxlan id, this is not a vxlan port
518 return
519
520 ifaceobjrunning.update_config('vxlan-port', vxlanattrs.get(Link.IFLA_VXLAN_PORT))
521
522 attrval = vxlanattrs.get('local')
523 if attrval:
524 ifaceobjrunning.update_config('vxlan-local-tunnelip', attrval)
525 attrval = vxlanattrs.get('svcnode')
526 if attrval:
527 ifaceobjrunning.update_config('vxlan-svcnode', attrval)
528 purge_remotes = self._get_purge_remotes(None)
529 if purge_remotes:
530 # if purge_remotes is on, it means we own the
531 # remote ips. Query them and add it to the running config
532 attrval = self.ipcmd.get_vxlan_peers(ifaceobjrunning.name, vxlanattrs.get('svcnode'))
533 if attrval:
534 [ifaceobjrunning.update_config('vxlan-remoteip', a)
535 for a in attrval]
536 attrval = vxlanattrs.get('learning')
537 if attrval and attrval == 'on':
538 ifaceobjrunning.update_config('vxlan-learning', 'on')
539 attrval = vxlanattrs.get('ageing')
540 if attrval:
541 ifaceobjrunning.update_config('vxlan-ageing', vxlanattrs.get('ageing'))
542
a382b488
JF
543 ifla_vxlan_link = vxlanattrs.get(Link.IFLA_VXLAN_LINK)
544 if ifla_vxlan_link:
545 ifaceobjrunning.update_config(
546 'vxlan-physdev',
547 netlink.get_iface_name(ifla_vxlan_link)
548 )
549
d486dd0d
JF
550 _run_ops = {'pre-up' : _up,
551 'post-down' : _down,
552 'query-checkcurr' : _query_check,
553 'query-running' : _query_running}
554
555 def get_ops(self):
556 return self._run_ops.keys()
557
558 def _init_command_handlers(self):
559 if not self.ipcmd:
560 self.ipcmd = LinkUtils()
561
562 def run(self, ifaceobj, operation, query_ifaceobj=None, **extra_args):
563 op_handler = self._run_ops.get(operation)
564 if not op_handler:
565 return
566 if (operation != 'query-running' and
567 not self._is_vxlan_device(ifaceobj)):
568 return
569 self._init_command_handlers()
570 if operation == 'query-checkcurr':
571 op_handler(self, ifaceobj, query_ifaceobj)
572 else:
573 op_handler(self, ifaceobj)