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