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