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