]> git.proxmox.com Git - mirror_ifupdown2.git/blob - ifupdown2/addons/vrf.py
addons: vrf: fix link-down yes on vrf throws an exception
[mirror_ifupdown2.git] / ifupdown2 / addons / vrf.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 import re
8 import os
9 import fcntl
10 import atexit
11 import signal
12
13 from sets import Set
14
15 try:
16 import ifupdown2.ifupdown.policymanager as policymanager
17 import ifupdown2.ifupdown.ifupdownflags as ifupdownflags
18
19 from ifupdown2.ifupdown.iface import *
20 from ifupdown2.ifupdown.utils import utils
21 from ifupdown2.ifupdown.netlink import netlink
22
23 from ifupdown2.ifupdownaddons.dhclient import dhclient
24 from ifupdown2.ifupdownaddons.utilsbase import *
25 from ifupdown2.ifupdownaddons.LinkUtils import LinkUtils
26 from ifupdown2.ifupdownaddons.modulebase import moduleBase
27 except ImportError:
28 import ifupdown.policymanager as policymanager
29 import ifupdown.ifupdownflags as ifupdownflags
30
31 from ifupdown.iface import *
32 from ifupdown.utils import utils
33 from ifupdown.netlink import netlink
34
35 from ifupdownaddons.dhclient import dhclient
36 from ifupdownaddons.utilsbase import *
37 from ifupdownaddons.LinkUtils import LinkUtils
38 from ifupdownaddons.modulebase import moduleBase
39
40
41 class vrfPrivFlags:
42 PROCESSED = 0x1
43
44 class vrf(moduleBase):
45 """ ifupdown2 addon module to configure vrfs """
46 _modinfo = { 'mhelp' : 'vrf configuration module',
47 'attrs' : {
48 'vrf-table':
49 {'help' : 'vrf device routing table id. key to ' +
50 'creating a vrf device. ' +
51 'Table id is either \'auto\' or '+
52 '\'valid routing table id\'',
53 'validvals': ['auto', '<number>'],
54 'example': ['vrf-table auto', 'vrf-table 1001']},
55 'vrf':
56 {'help' : 'vrf the interface is part of.',
57 'validvals': ['<text>'],
58 'example': ['vrf blue']}}}
59
60 iproute2_vrf_filename = '/etc/iproute2/rt_tables.d/ifupdown2_vrf_map.conf'
61 iproute2_vrf_filehdr = '# This file is autogenerated by ifupdown2.\n' + \
62 '# It contains the vrf name to table mapping.\n' + \
63 '# Reserved table range %s %s\n'
64 VRF_TABLE_START = 1001
65 VRF_TABLE_END = 5000
66
67 system_reserved_rt_tables = {'255' : 'local', '254' : 'main',
68 '253' : 'default', '0' : 'unspec'}
69
70 def __init__(self, *args, **kargs):
71 moduleBase.__init__(self, *args, **kargs)
72 self.ipcmd = None
73 self.bondcmd = None
74 self.dhclientcmd = None
75 self.name = self.__class__.__name__
76 self.vrf_mgmt_devname = policymanager.policymanager_api.get_module_globals(module_name=self.__class__.__name__, attr='vrf-mgmt-devname')
77
78 self.user_reserved_vrf_table = []
79
80 if (ifupdownflags.flags.PERFMODE and
81 not (self.vrf_mgmt_devname and os.path.exists('/sys/class/net/%s'
82 %self.vrf_mgmt_devname))):
83 # if perf mode is set (PERFMODE is set at boot), and this is the first
84 # time we are calling ifup at boot (check for mgmt vrf existance at
85 # boot, make sure this is really the first invocation at boot.
86 # ifup is called with PERFMODE at boot multiple times (once for mgmt vrf
87 # and the second time with all auto interfaces). We want to delete
88 # the map file only the first time. This is to avoid accidently
89 # deleting map file with a valid mgmt vrf entry
90 if os.path.exists(self.iproute2_vrf_filename):
91 try:
92 self.logger.info('vrf: removing file %s'
93 %self.iproute2_vrf_filename)
94 os.remove(self.iproute2_vrf_filename)
95 except Exception, e:
96 self.logger.debug('vrf: removing file failed (%s)'
97 %str(e))
98 try:
99 ip_rules = utils.exec_command('%s rule show'
100 %utils.ip_cmd).splitlines()
101 self.ip_rule_cache = [' '.join(r.split()) for r in ip_rules]
102 except Exception, e:
103 self.ip_rule_cache = []
104 self.logger.warn('vrf: cache v4: %s' % str(e))
105
106 try:
107 ip_rules = utils.exec_command('%s -6 rule show'
108 %utils.ip_cmd).splitlines()
109 self.ip6_rule_cache = [' '.join(r.split()) for r in ip_rules]
110 except Exception, e:
111 self.ip6_rule_cache = []
112 self.logger.warn('vrf: cache v6: %s' % str(e))
113
114 #self.logger.debug("vrf: ip rule cache")
115 #self.logger.info(self.ip_rule_cache)
116
117 #self.logger.info("vrf: ip -6 rule cache")
118 #self.logger.info(self.ip6_rule_cache)
119
120 self.l3mdev_checked = False
121 self.l3mdev4_rule = False
122 if self._l3mdev_rule(self.ip_rule_cache):
123 self.l3mdev4_rule = True
124 self.l3mdev_checked = True
125 self.l3mdev6_rule = False
126 if self._l3mdev_rule(self.ip6_rule_cache):
127 self.l3mdev6_rule = True
128 self.l3mdev_checked = True
129 self._iproute2_vrf_map_initialized = False
130 self.iproute2_vrf_map = {}
131 self.iproute2_vrf_map_fd = None
132 self.iproute2_vrf_map_sync_to_disk = False
133
134 self.vrf_table_id_start = policymanager.policymanager_api.get_module_globals(module_name=self.__class__.__name__, attr='vrf-table-id-start')
135 if not self.vrf_table_id_start:
136 self.vrf_table_id_start = self.VRF_TABLE_START
137 self.vrf_table_id_end = policymanager.policymanager_api.get_module_globals(module_name=self.__class__.__name__, attr='vrf-table-id-end')
138 if not self.vrf_table_id_end:
139 self.vrf_table_id_end = self.VRF_TABLE_END
140
141 self._modinfo['attrs']['vrf-table']['validrange'] = [
142 str(self.vrf_table_id_start),
143 str(self.vrf_table_id_end)
144 ]
145
146 self.vrf_max_count = policymanager.policymanager_api.get_module_globals(module_name=self.__class__.__name__, attr='vrf-max-count')
147
148 self.vrf_fix_local_table = True
149 self.vrf_count = 0
150 self.vrf_helper = policymanager.policymanager_api.get_module_globals(module_name=self.__class__.__name__, attr='vrf-helper')
151 self.vrf_close_socks_on_down = policymanager.policymanager_api.get_module_globals(module_name=self.__class__.__name__, attr='vrf-close-socks-on-down')
152 self.warn_on_vrf_map_write_err = True
153
154 def _check_vrf_table_id(self, ifaceobj):
155 vrf_table = ifaceobj.get_attr_value_first('vrf-table')
156 if not vrf_table:
157 return False
158 if (vrf_table != 'auto' and
159 (int(vrf_table) < self.vrf_table_id_start or
160 int(vrf_table) > self.vrf_table_id_end)):
161 self.logger.error('%s: vrf table id %s out of reserved range [%d,%d]'
162 %(ifaceobj.name,
163 vrf_table,
164 self.vrf_table_id_start,
165 self.vrf_table_id_end))
166 return False
167 return True
168
169 def syntax_check(self, ifaceobj, ifaceobj_getfunc):
170 if ifaceobj.link_kind & ifaceLinkKind.VRF:
171 try:
172 check_vrf_table_id = self._check_vrf_table_id(ifaceobj)
173 check_vrf_sys_names = self._check_vrf_system_reserved_names(ifaceobj)
174 return check_vrf_table_id and check_vrf_sys_names
175 except Exception as e:
176 self.logger.error('%s: %s' % (ifaceobj.name, str(e)))
177 return False
178 return True
179
180 def _check_vrf_system_reserved_names(self, ifaceobj):
181 system_reserved_names = self.system_reserved_rt_tables.values()
182 if ifaceobj.name in system_reserved_names:
183 self.log_error('cannot use system reserved %s vrf names'
184 % (str(system_reserved_names)), ifaceobj)
185 return False
186 return True
187
188 def _iproute2_vrf_map_initialize(self, writetodisk=True):
189 if self._iproute2_vrf_map_initialized:
190 return
191
192 # XXX: check for vrf reserved overlap in /etc/iproute2/rt_tables
193 self.iproute2_vrf_map = {}
194 iproute2_vrf_map_force_rewrite = False
195 # read or create /etc/iproute2/rt_tables.d/ifupdown2.vrf_map
196 if os.path.exists(self.iproute2_vrf_filename):
197 with open(self.iproute2_vrf_filename, 'r+') as vrf_map_fd:
198 lines = vrf_map_fd.readlines()
199 for l in lines:
200 l = l.strip()
201 if l[0] == '#':
202 continue
203 try:
204 (table, vrf_name) = l.strip().split()
205 if self.iproute2_vrf_map.get(int(table)):
206 # looks like the existing file has
207 # duplicate entries, force rewrite of the
208 # file
209 iproute2_vrf_map_force_rewrite = True
210 continue
211 self.iproute2_vrf_map[int(table)] = vrf_name
212 except Exception, e:
213 self.logger.info('vrf: iproute2_vrf_map: unable to parse %s (%s)' %(l, str(e)))
214 pass
215
216 vrfs = self.ipcmd.link_get_vrfs()
217 running_vrf_map = {}
218 if vrfs:
219 for v, lattrs in vrfs.iteritems():
220 table = lattrs.get('table', None)
221 if table:
222 running_vrf_map[int(table)] = v
223
224 if (not running_vrf_map or (running_vrf_map != self.iproute2_vrf_map)):
225 self.iproute2_vrf_map = running_vrf_map
226 iproute2_vrf_map_force_rewrite = True
227
228 self.iproute2_vrf_map_fd = None
229 if writetodisk:
230 if iproute2_vrf_map_force_rewrite:
231 # reopen the file and rewrite the map
232 self._iproute2_vrf_map_open(True, False)
233 else:
234 self._iproute2_vrf_map_open(False, True)
235
236 self.iproute2_vrf_map_sync_to_disk = False
237 atexit.register(self._iproute2_vrf_map_sync_to_disk)
238
239 self.logger.info("vrf: dumping iproute2_vrf_map")
240 self.logger.info(self.iproute2_vrf_map)
241
242 last_used_vrf_table = None
243 for t in range(self.vrf_table_id_start,
244 self.vrf_table_id_end):
245 if not self.iproute2_vrf_map.get(t):
246 break
247 last_used_vrf_table = t
248 self.last_used_vrf_table = last_used_vrf_table
249 self._iproute2_vrf_map_initialized = True
250 self.vrf_count = len(self.iproute2_vrf_map)
251
252 def _iproute2_map_warn(self, errstr):
253 if self.warn_on_vrf_map_write_err:
254 if not os.path.exists('/etc/iproute2/rt_tables.d/'):
255 self.logger.info('unable to save iproute2 vrf to table ' +
256 'map (%s)\n' %errstr)
257 self.logger.info('cannot find /etc/iproute2/rt_tables.d.' +
258 ' pls check if your iproute2 version' +
259 ' supports rt_tables.d')
260 else:
261 self.logger.warn('unable to open iproute2 vrf to table ' +
262 'map (%s)\n' %errstr)
263 self.warn_on_vrf_map_write_err = False
264
265 def _iproute2_vrf_map_sync_to_disk(self):
266 if (ifupdownflags.flags.DRYRUN or
267 not self.iproute2_vrf_map_sync_to_disk):
268 return
269 self.logger.info('vrf: syncing table map to %s'
270 %self.iproute2_vrf_filename)
271 try:
272 with open(self.iproute2_vrf_filename, 'w') as f:
273 f.write(self.iproute2_vrf_filehdr %(self.vrf_table_id_start,
274 self.vrf_table_id_end))
275 for t, v in self.iproute2_vrf_map.iteritems():
276 f.write('%s %s\n' %(t, v))
277 f.flush()
278 except Exception, e:
279 self._iproute2_map_warn(str(e))
280 pass
281
282 def _iproute2_vrf_map_open(self, sync_vrfs=False, append=False):
283 self.logger.info('vrf: syncing table map to %s'
284 %self.iproute2_vrf_filename)
285 if ifupdownflags.flags.DRYRUN:
286 return
287 fmode = 'a+' if append else 'w'
288 try:
289 self.iproute2_vrf_map_fd = open(self.iproute2_vrf_filename,
290 '%s' %fmode)
291 fcntl.fcntl(self.iproute2_vrf_map_fd, fcntl.F_SETFD, fcntl.FD_CLOEXEC)
292 except Exception, e:
293 self._iproute2_map_warn(str(e))
294 return
295
296 if not append:
297 # write file header
298 self.iproute2_vrf_map_fd.write(self.iproute2_vrf_filehdr
299 %(self.vrf_table_id_start,
300 self.vrf_table_id_end))
301 for t, v in self.iproute2_vrf_map.iteritems():
302 self.iproute2_vrf_map_fd.write('%s %s\n' %(t, v))
303 self.iproute2_vrf_map_fd.flush()
304
305 def _is_vrf(self, ifaceobj):
306 if ifaceobj.get_attr_value_first('vrf-table'):
307 return True
308 return False
309
310 def get_upper_ifacenames(self, ifaceobj, ifacenames_all=None):
311 """ Returns list of interfaces dependent on ifaceobj """
312
313 vrf_table = ifaceobj.get_attr_value_first('vrf-table')
314 if vrf_table:
315 ifaceobj.link_type = ifaceLinkType.LINK_MASTER
316 ifaceobj.link_kind |= ifaceLinkKind.VRF
317 ifaceobj.role |= ifaceRole.MASTER
318
319 if vrf_table != 'auto':
320 # if the user didn't specify auto we need to store the desired
321 # vrf tables ids, in case the configuration has both auto and
322 # hardcoded vrf-table ids. We need to create them all without
323 # collisions.
324 self.user_reserved_vrf_table.append(int(vrf_table))
325
326 vrf_iface_name = ifaceobj.get_attr_value_first('vrf')
327 if not vrf_iface_name:
328 return None
329 ifaceobj.link_type = ifaceLinkType.LINK_SLAVE
330 ifaceobj.link_privflags |= ifaceLinkPrivFlags.VRF_SLAVE
331
332 return [vrf_iface_name]
333
334 def get_upper_ifacenames_running(self, ifaceobj):
335 return None
336
337 def _get_iproute2_vrf_table(self, vrf_dev_name):
338 for t, v in self.iproute2_vrf_map.iteritems():
339 if v == vrf_dev_name:
340 return str(t)
341 return None
342
343 def _get_avail_vrf_table_id(self):
344 if self.last_used_vrf_table == None:
345 table_id_start = self.vrf_table_id_start
346 else:
347 table_id_start = self.last_used_vrf_table + 1
348 for t in range(table_id_start, self.vrf_table_id_end):
349 if (not self.iproute2_vrf_map.get(t)
350 and t not in self.user_reserved_vrf_table):
351 self.last_used_vrf_table = t
352 return str(t)
353 return None
354
355 def _iproute2_is_vrf_tableid_inuse(self, vrfifaceobj, table_id):
356 old_vrf_name = self.iproute2_vrf_map.get(int(table_id))
357 if old_vrf_name and old_vrf_name != vrfifaceobj.name:
358 self.log_error('table id %s already assigned to vrf dev %s'
359 %(table_id, old_vrf_name), vrfifaceobj)
360
361 def _iproute2_vrf_table_entry_add(self, vrfifaceobj, table_id):
362 old_vrf_name = self.iproute2_vrf_map.get(int(table_id))
363 if not old_vrf_name:
364 self.iproute2_vrf_map[int(table_id)] = vrfifaceobj.name
365 if self.iproute2_vrf_map_fd:
366 self.iproute2_vrf_map_fd.write('%s %s\n'
367 %(table_id, vrfifaceobj.name))
368 self.iproute2_vrf_map_fd.flush()
369 self.vrf_count += 1
370 return
371 if old_vrf_name != vrfifaceobj.name:
372 self.log_error('table id %d already assigned to vrf dev %s'
373 %(table_id, old_vrf_name))
374
375 def _iproute2_vrf_table_entry_del(self, table_id):
376 try:
377 # with any del of vrf map, we need to force sync to disk
378 self.iproute2_vrf_map_sync_to_disk = True
379 del self.iproute2_vrf_map[int(table_id)]
380 except Exception, e:
381 self.logger.info('vrf: iproute2 vrf map del failed for %s (%s)'
382 %(table_id, str(e)))
383 pass
384
385 def _is_vrf_dev(self, ifacename):
386 # Look at iproute2 map for now.
387 # If it was a master we knew about,
388 # it is definately there
389 if ifacename in self.iproute2_vrf_map.values():
390 return True
391 return False
392
393 def _is_dhcp_slave(self, ifaceobj):
394 if (not ifaceobj.addr_method or
395 (ifaceobj.addr_method != 'dhcp' and
396 ifaceobj.addr_method != 'dhcp6')):
397 return False
398 return True
399
400 def _up_vrf_slave_without_master(self, ifacename, vrfname, ifaceobj, vrf_master_objs, ifaceobj_getfunc=None):
401 """ If we have a vrf slave that has dhcp configured, bring up the
402 vrf master now. This is needed because vrf has special handling
403 in dhclient hook which requires the vrf master to be present """
404 vrf_master = None
405 if len(ifaceobj.upperifaces) > 1 and ifaceobj_getfunc:
406 for upper_iface in ifaceobj.upperifaces:
407 upper_ifaceobjs = ifaceobj_getfunc(upper_iface)
408
409 if upper_ifaceobjs:
410 for upper_obj in upper_ifaceobjs:
411 if upper_obj.link_kind & ifaceLinkKind.VRF:
412 vrf_master = upper_obj.name
413 break
414 elif ifaceobj.upperifaces:
415 vrf_master = ifaceobj.upperifaces[0]
416 if not vrf_master:
417 self.logger.warn('%s: vrf master not found' %ifacename)
418 return
419 if os.path.exists('/sys/class/net/%s' %vrf_master):
420 self.logger.info('%s: vrf master %s exists returning'
421 %(ifacename, vrf_master))
422 return
423 self.logger.info('%s: bringing up vrf master %s'
424 %(ifacename, vrf_master))
425 for mobj in vrf_master_objs:
426 vrf_table = mobj.get_attr_value_first('vrf-table')
427 if vrf_table:
428 if vrf_table == 'auto':
429 vrf_table = self._get_avail_vrf_table_id()
430 if not vrf_table:
431 self.log_error('%s: unable to get an auto table id'
432 %mobj.name, ifaceobj)
433 self.logger.info('%s: table id auto: selected table id %s\n'
434 %(mobj.name, vrf_table))
435 try:
436 self._up_vrf_dev(mobj, vrf_table, False)
437 except Exception:
438 raise
439 break
440 self._handle_existing_connections(ifaceobj, vrfname)
441 self.ipcmd.link_set(ifacename, 'master', vrfname)
442 return
443
444 def _down_dhcp_slave(self, ifaceobj, vrfname):
445 try:
446 dhclient_cmd_prefix = None
447 if (vrfname and self.vrf_exec_cmd_prefix and
448 self.ipcmd.link_exists(vrfname)):
449 dhclient_cmd_prefix = '%s %s' %(self.vrf_exec_cmd_prefix,
450 vrfname)
451 self.dhclientcmd.release(ifaceobj.name, dhclient_cmd_prefix)
452 except:
453 # ignore any dhclient release errors
454 pass
455
456 def _handle_existing_connections(self, ifaceobj, vrfname):
457 if not ifaceobj or ifupdownflags.flags.PERFMODE:
458 return
459 if (self.vrf_mgmt_devname and
460 self.vrf_mgmt_devname == vrfname):
461 self._kill_ssh_connections(ifaceobj.name)
462 if self._is_dhcp_slave(ifaceobj):
463 self._down_dhcp_slave(ifaceobj, vrfname)
464
465 def _up_vrf_slave(self, ifacename, vrfname, ifaceobj=None,
466 ifaceobj_getfunc=None, vrf_exists=False):
467 try:
468 master_exists = True
469 if vrf_exists or self.ipcmd.link_exists(vrfname):
470 uppers = self.ipcmd.link_get_uppers(ifacename)
471 if not uppers or vrfname not in uppers:
472 self._handle_existing_connections(ifaceobj, vrfname)
473 self.ipcmd.link_set(ifacename, 'master', vrfname)
474 elif ifaceobj:
475 vrf_master_objs = ifaceobj_getfunc(vrfname)
476 if not vrf_master_objs:
477 # this is the case where vrf is assigned to an interface
478 # but user has not provided a vrf interface.
479 # people expect you to warn them but go ahead with the
480 # rest of the config on that interface
481 netlink.link_set_updown(ifacename, "up")
482 self.log_error('vrf master ifaceobj %s not found'
483 %vrfname)
484 return
485 if (ifupdownflags.flags.ALL or
486 ifupdownflags.flags.WITH_DEPENDS or
487 (ifupdownflags.flags.CLASS and
488 ifaceobj.classes and vrf_master_objs[0].classes and
489 Set(ifaceobj.classes).intersection(vrf_master_objs[0].classes))):
490 self._up_vrf_slave_without_master(ifacename, vrfname,
491 ifaceobj,
492 vrf_master_objs,
493 ifaceobj_getfunc)
494 else:
495 master_exists = False
496 else:
497 master_exists = False
498 if master_exists:
499 if not ifaceobj.link_privflags & ifaceLinkPrivFlags.KEEP_LINK_DOWN:
500 netlink.link_set_updown(ifacename, "up")
501 else:
502 self.log_error('vrf %s not around, skipping vrf config'
503 %(vrfname), ifaceobj)
504 except Exception, e:
505 self.log_error('%s: %s' %(ifacename, str(e)), ifaceobj)
506
507 def _del_vrf_rules(self, vrf_dev_name, vrf_table):
508 pref = 200
509 ip_rule_out_format = '%s: from all %s %s lookup %s'
510 ip_rule_cmd = '%s %s rule del pref %s %s %s table %s'
511
512 rule = ip_rule_out_format %(pref, 'oif', vrf_dev_name, vrf_dev_name)
513 if rule in self.ip_rule_cache:
514 rule_cmd = ip_rule_cmd %(utils.ip_cmd,
515 '', pref, 'oif', vrf_dev_name,
516 vrf_dev_name)
517 utils.exec_command(rule_cmd)
518
519 rule = ip_rule_out_format %(pref, 'iif', vrf_dev_name, vrf_dev_name)
520 if rule in self.ip_rule_cache:
521 rule_cmd = ip_rule_cmd %(utils.ip_cmd,
522 '', pref, 'iif', vrf_dev_name,
523 vrf_dev_name)
524 utils.exec_command(rule_cmd)
525
526 rule = ip_rule_out_format %(pref, 'oif', vrf_dev_name, vrf_dev_name)
527 if rule in self.ip6_rule_cache:
528 rule_cmd = ip_rule_cmd %(utils.ip_cmd,
529 '-6', pref, 'oif', vrf_dev_name,
530 vrf_dev_name)
531 utils.exec_command(rule_cmd)
532
533 rule = ip_rule_out_format %(pref, 'iif', vrf_dev_name, vrf_dev_name)
534 if rule in self.ip6_rule_cache:
535 rule_cmd = ip_rule_cmd %(utils.ip_cmd,
536 '-6', pref, 'iif', vrf_dev_name,
537 vrf_dev_name)
538 utils.exec_command(rule_cmd)
539
540 def _l3mdev_rule(self, ip_rules):
541 for rule in ip_rules:
542 if not re.search(r"\d.*from\s+all\s+lookup\s+\W?l3mdev-table\W?",
543 rule):
544 continue
545 return True
546 return False
547
548 def _rule_cache_fill(self):
549 ip_rules = utils.exec_command('%s rule show'
550 %utils.ip_cmd).splitlines()
551 self.ip_rule_cache = [' '.join(r.split()) for r in ip_rules]
552 self.l3mdev4_rule = self._l3mdev_rule(self.ip_rule_cache)
553 ip_rules = utils.exec_command('%s -6 rule show'
554 %utils.ip_cmd).splitlines()
555 self.ip6_rule_cache = [' '.join(r.split()) for r in ip_rules]
556 self.l3mdev6_rule = self._l3mdev_rule(self.ip6_rule_cache)
557
558 def _add_vrf_rules(self, vrf_dev_name, vrf_table):
559 pref = 200
560 ip_rule_out_format = '%s: from all %s %s lookup %s'
561 ip_rule_cmd = '%s %s rule add pref %s %s %s table %s'
562 if self.vrf_fix_local_table:
563 self.vrf_fix_local_table = False
564 rule = '0: from all lookup local'
565 if rule in self.ip_rule_cache:
566 try:
567 utils.exec_command('%s rule del pref 0'
568 %utils.ip_cmd)
569 utils.exec_command('%s rule add pref 32765 table local'
570 %utils.ip_cmd)
571 except Exception, e:
572 self.logger.info('%s: %s' % (vrf_dev_name, str(e)))
573 pass
574 if rule in self.ip6_rule_cache:
575 try:
576 utils.exec_command('%s -6 rule del pref 0'
577 %utils.ip_cmd)
578 utils.exec_command('%s -6 rule add pref 32765 table local'
579 %utils.ip_cmd)
580 except Exception, e:
581 self.logger.info('%s: %s' % (vrf_dev_name, str(e)))
582 pass
583
584 if not self.l3mdev_checked:
585 self._rule_cache_fill()
586 self.l3mdev_checked = True
587 #Example ip rule
588 #200: from all oif blue lookup blue
589 #200: from all iif blue lookup blue
590
591 rule = ip_rule_out_format %(pref, 'oif', vrf_dev_name, vrf_dev_name)
592 if not self.l3mdev4_rule and rule not in self.ip_rule_cache:
593 rule_cmd = ip_rule_cmd %(utils.ip_cmd,
594 '', pref, 'oif', vrf_dev_name,
595 vrf_dev_name)
596 utils.exec_command(rule_cmd)
597
598 rule = ip_rule_out_format %(pref, 'iif', vrf_dev_name, vrf_dev_name)
599 if not self.l3mdev4_rule and rule not in self.ip_rule_cache:
600 rule_cmd = ip_rule_cmd %(utils.ip_cmd,
601 '', pref, 'iif', vrf_dev_name,
602 vrf_dev_name)
603 utils.exec_command(rule_cmd)
604
605 rule = ip_rule_out_format %(pref, 'oif', vrf_dev_name, vrf_dev_name)
606 if not self.l3mdev6_rule and rule not in self.ip6_rule_cache:
607 rule_cmd = ip_rule_cmd %(utils.ip_cmd,
608 '-6', pref, 'oif', vrf_dev_name,
609 vrf_dev_name)
610 utils.exec_command(rule_cmd)
611
612 rule = ip_rule_out_format %(pref, 'iif', vrf_dev_name, vrf_dev_name)
613 if not self.l3mdev6_rule and rule not in self.ip6_rule_cache:
614 rule_cmd = ip_rule_cmd %(utils.ip_cmd,
615 '-6', pref, 'iif', vrf_dev_name,
616 vrf_dev_name)
617 utils.exec_command(rule_cmd)
618
619 def _is_address_virtual_slaves(self, vrfobj, config_vrfslaves,
620 vrfslave):
621 # Address virtual lines on a vrf slave will create
622 # macvlan devices on the vrf slave and enslave them
623 # to the vrf master. This function checks if the
624 # vrf slave is such a macvlan interface.
625 # XXX: additional possible checks that can be done here
626 # are:
627 # - check if it is also a macvlan device of the
628 # format <vrf_slave>-v<int> created by the
629 # address virtual module
630 vrfslave_lowers = self.ipcmd.link_get_lowers(vrfslave)
631 if vrfslave_lowers:
632 if vrfslave_lowers[0] in config_vrfslaves:
633 return True
634 return False
635
636 def _add_vrf_slaves(self, ifaceobj, ifaceobj_getfunc=None):
637 running_slaves = self.ipcmd.link_get_lowers(ifaceobj.name)
638 config_slaves = ifaceobj.lowerifaces
639 if not config_slaves and not running_slaves:
640 return
641
642 if not config_slaves: config_slaves = []
643 if not running_slaves: running_slaves = []
644 add_slaves = set(config_slaves).difference(set(running_slaves))
645 del_slaves = set(running_slaves).difference(set(config_slaves))
646 if add_slaves:
647 for s in add_slaves:
648 try:
649 if not self.ipcmd.link_exists(s):
650 continue
651 sobj = None
652 if ifaceobj_getfunc:
653 sobj = ifaceobj_getfunc(s)
654 self._up_vrf_slave(s, ifaceobj.name,
655 sobj[0] if sobj else None,
656 ifaceobj_getfunc, True)
657 except Exception, e:
658 self.logger.info('%s: %s' %(ifaceobj.name, str(e)))
659
660 if del_slaves:
661 for s in del_slaves:
662 try:
663 if self._is_address_virtual_slaves(ifaceobj,
664 config_slaves, s):
665 continue
666 sobj = None
667 if ifaceobj_getfunc:
668 sobj = ifaceobj_getfunc(s)
669 self._down_vrf_slave(s, sobj[0] if sobj else None,
670 ifaceobj.name)
671 except Exception, e:
672 self.logger.info('%s: %s' %(ifaceobj.name, str(e)))
673
674 if ifaceobj.link_type == ifaceLinkType.LINK_MASTER:
675 for s in config_slaves:
676 try:
677 for slave_ifaceobj in ifaceobj_getfunc(s) or []:
678 if ifaceobj.link_privflags & ifaceLinkPrivFlags.KEEP_LINK_DOWN:
679 raise Exception('%s: slave configured with link-down yes')
680 netlink.link_set_updown(s, "up")
681 except Exception, e:
682 self.logger.debug('%s: %s' % (ifaceobj.name, str(e)))
683 pass
684
685 def _set_vrf_dev_processed_flag(self, ifaceobj):
686 ifaceobj.module_flags[self.name] = \
687 ifaceobj.module_flags.setdefault(self.name, 0) | \
688 vrfPrivFlags.PROCESSED
689
690 def _check_vrf_dev_processed_flag(self, ifaceobj):
691 if (ifaceobj.module_flags.get(self.name, 0) & vrfPrivFlags.PROCESSED):
692 return True
693 return False
694
695 def _create_vrf_dev(self, ifaceobj, vrf_table):
696 if not self.ipcmd.link_exists(ifaceobj.name):
697 self._check_vrf_system_reserved_names(ifaceobj)
698
699 if self.vrf_count == self.vrf_max_count:
700 self.log_error('max vrf count %d hit...not '
701 'creating vrf' % self.vrf_count, ifaceobj)
702 if vrf_table == 'auto':
703 vrf_table = self._get_avail_vrf_table_id()
704 if not vrf_table:
705 self.log_error('unable to get an auto table id', ifaceobj)
706 self.logger.info('%s: table id auto: selected table id %s\n'
707 %(ifaceobj.name, vrf_table))
708 else:
709 self._iproute2_is_vrf_tableid_inuse(ifaceobj, vrf_table)
710 if ifaceobj.name in self.system_reserved_rt_tables.keys():
711 self.log_error('cannot use system reserved %s table ids'
712 %(str(self.system_reserved_rt_tables.keys())),
713 ifaceobj)
714
715 if not vrf_table.isdigit():
716 self.log_error('vrf-table must be an integer or \'auto\'', ifaceobj)
717
718 # XXX: If we decide to not allow vrf id usages out of
719 # the reserved ifupdown range, then uncomment this code.
720 else:
721 if (int(vrf_table) < self.vrf_table_id_start or
722 int(vrf_table) > self.vrf_table_id_end):
723 self.log_error('vrf table id %s out of reserved range [%d,%d]'
724 %(vrf_table,
725 self.vrf_table_id_start,
726 self.vrf_table_id_end), ifaceobj)
727 try:
728 self.ipcmd.link_create(ifaceobj.name, 'vrf',
729 {'table' : '%s' %vrf_table})
730 except Exception, e:
731 self.log_error('create failed (%s)\n' % str(e), ifaceobj)
732 if vrf_table != 'auto':
733 self._iproute2_vrf_table_entry_add(ifaceobj, vrf_table)
734 else:
735 if vrf_table == 'auto':
736 vrf_table = self._get_iproute2_vrf_table(ifaceobj.name)
737 if not vrf_table and not ifupdownflags.flags.DRYRUN:
738 self.log_error('unable to get vrf table id', ifaceobj)
739
740 # if the device exists, check if table id is same
741 vrfdev_attrs = self.ipcmd.link_get_linkinfo_attrs(ifaceobj.name)
742 if vrfdev_attrs:
743 running_table = vrfdev_attrs.get('table', None)
744 if vrf_table != running_table:
745 self.log_error('cannot change vrf table id,running table id'
746 ' %s is different from config id %s'
747 % (running_table, vrf_table), ifaceobj)
748 return vrf_table
749
750 def _up_vrf_helper(self, ifaceobj, vrf_table):
751 mode = ""
752 if ifupdownflags.flags.PERFMODE:
753 mode = "boot"
754 if self.vrf_helper:
755 utils.exec_command('%s create %s %s %s' %
756 (self.vrf_helper,
757 ifaceobj.name,
758 vrf_table,
759 mode))
760
761 def _up_vrf_dev(self, ifaceobj, vrf_table, add_slaves=True,
762 ifaceobj_getfunc=None):
763
764 # if vrf dev is already processed return. This can happen
765 # if we the slave was configured before.
766 # see self._up_vrf_slave_without_master
767 if self._check_vrf_dev_processed_flag(ifaceobj):
768 return True
769
770 try:
771 vrf_table = self._create_vrf_dev(ifaceobj, vrf_table)
772 except Exception, e:
773 self.log_error('%s: %s' %(ifaceobj.name, str(e)), ifaceobj)
774
775 try:
776 self._add_vrf_rules(ifaceobj.name, vrf_table)
777 self._up_vrf_helper(ifaceobj, vrf_table)
778 if add_slaves:
779 self._add_vrf_slaves(ifaceobj, ifaceobj_getfunc)
780 self._set_vrf_dev_processed_flag(ifaceobj)
781
782 if not ifaceobj.link_privflags & ifaceLinkPrivFlags.KEEP_LINK_DOWN:
783 netlink.link_set_updown(ifaceobj.name, "up")
784 except Exception, e:
785 self.log_error('%s: %s' %(ifaceobj.name, str(e)), ifaceobj)
786
787 def _kill_ssh_connections(self, ifacename):
788 try:
789 runningaddrsdict = self.ipcmd.get_running_addrs(None, ifacename)
790 if not runningaddrsdict:
791 return
792 iplist = [i.split('/', 1)[0] for i in runningaddrsdict.keys()]
793 if not iplist:
794 return
795 proc=[]
796 #Example output:
797 #ESTAB 0 0 10.0.1.84:ssh 10.0.1.228:45186
798 #users:(("sshd",pid=2528,fd=3))
799 cmdl = [utils.ss_cmd, '-t', '-p']
800 for line in utils.exec_commandl(cmdl).splitlines():
801 citems = line.split()
802 addr = None
803 if '%' in citems[3]:
804 addr = citems[3].split('%')[0]
805 elif ':ssh' in citems[3]:
806 addr = citems[3].split(':')[0]
807 if not addr:
808 continue
809 if addr in iplist:
810 if len(citems) == 6:
811 proc.append(citems[5].split(',')[1].split('=')[1])
812
813 if not proc:
814 return
815 pid = None
816 # outpt of '/usr/bin/pstree -Aps <pid>':
817 # 'systemd(1)---sshd(990)---sshd(16112)---sshd(16126)---bash(16127)---sudo(16756)---ifreload(16761)---pstree(16842)\n'
818 # get the above output to following format
819 # ['systemd(1)', 'sshd(990)', 'sshd(16112)', 'sshd(16126)', 'bash(16127)', 'sudo(16756)', 'ifreload(16761)', 'pstree(16850)']
820 pstree = list(reversed(utils.exec_command('%s -Aps %s' %
821 (utils.pstree_cmd, os.getpid())).strip().split('---')))
822 for index, process in enumerate(pstree):
823 # check the parent of SSH process to make sure
824 # we don't kill SSH server or systemd process
825 if 'sshd' in process and 'sshd' in pstree[index + 1]:
826 pid = filter(lambda x: x.isdigit(), process)
827 break
828 self.logger.info("%s: killing active ssh sessions: %s"
829 %(ifacename, str(proc)))
830
831 if ifupdownflags.flags.DRYRUN:
832 return
833 for id in proc:
834 if id != pid:
835 try:
836 os.kill(int(id), signal.SIGINT)
837 except OSError as e:
838 continue
839
840 # Kill current SSH client
841 if pid in proc:
842 try:
843 forkret = os.fork()
844 except OSError, e:
845 self.logger.info("fork error : %s [%d]" % (e.strerror, e.errno))
846 if (forkret == 0): # The first child.
847 try:
848 os.setsid()
849 self.logger.info("%s: ifreload continuing in the background" %ifacename)
850 except OSError, (err_no, err_message):
851 self.logger.info("os.setsid failed: errno=%d: %s" % (err_no, err_message))
852 self.logger.info("pid=%d pgid=%d" % (os.getpid(), os.getpgid(0)))
853 try:
854 self.logger.info("%s: killing our session: %s"
855 %(ifacename, str(proc)))
856 os.kill(int(pid), signal.SIGINT)
857 return
858 except OSError as e:
859 return
860 except Exception, e:
861 self.logger.info('%s: %s' %(ifacename, str(e)))
862
863 def _up(self, ifaceobj, ifaceobj_getfunc=None):
864 try:
865 vrf_table = ifaceobj.get_attr_value_first('vrf-table')
866 if vrf_table:
867 self._iproute2_vrf_map_initialize()
868 # This is a vrf device
869 self._up_vrf_dev(ifaceobj, vrf_table, True, ifaceobj_getfunc)
870 else:
871 vrf = ifaceobj.get_attr_value_first('vrf')
872 if vrf:
873 self._iproute2_vrf_map_initialize()
874 # This is a vrf slave
875 self._up_vrf_slave(ifaceobj.name, vrf, ifaceobj,
876 ifaceobj_getfunc)
877 elif not ifupdownflags.flags.PERFMODE:
878 # check if we were a slave before
879 master = self.ipcmd.link_get_master(ifaceobj.name)
880 if master:
881 self._iproute2_vrf_map_initialize()
882 if self._is_vrf_dev(master):
883 self._down_vrf_slave(ifaceobj.name, ifaceobj,
884 master)
885 except Exception, e:
886 self.log_error(str(e), ifaceobj)
887
888 def _down_vrf_helper(self, ifaceobj, vrf_table):
889 mode = ""
890 if ifupdownflags.flags.PERFMODE:
891 mode = "boot"
892 if self.vrf_helper:
893 utils.exec_command('%s delete %s %s %s' %
894 (self.vrf_helper,
895 ifaceobj.name,
896 vrf_table,
897 mode))
898
899 def _close_sockets(self, ifaceobj, ifindex):
900 if not self.vrf_close_socks_on_down:
901 return
902
903 try:
904 utils.exec_command('%s -aK \"dev == %s\"'
905 %(utils.ss_cmd, ifindex))
906 except Exception, e:
907 self.logger.info('%s: closing socks using ss'
908 ' failed (%s)\n' %(ifaceobj.name, str(e)))
909 pass
910
911 def _down_vrf_dev(self, ifaceobj, vrf_table, ifaceobj_getfunc=None):
912
913 if not self.ipcmd.link_exists(ifaceobj.name):
914 return
915
916 if vrf_table == 'auto':
917 vrf_table = self._get_iproute2_vrf_table(ifaceobj.name)
918
919 running_slaves = self.ipcmd.link_get_lowers(ifaceobj.name)
920 if running_slaves:
921 for s in running_slaves:
922 if ifaceobj_getfunc:
923 sobj = ifaceobj_getfunc(s)
924 try:
925 self._handle_existing_connections(sobj[0]
926 if sobj else None,
927 ifaceobj.name)
928 except Exception, e:
929 self.logger.info('%s: %s' %(ifaceobj.name, str(e)))
930 pass
931 try:
932 self.ipcmd.addr_flush(s)
933 netlink.link_set_updown(s, "down")
934 except Exception, e:
935 self.logger.info('%s: %s' %(ifaceobj.name, str(e)))
936 pass
937
938 try:
939 self._down_vrf_helper(ifaceobj, vrf_table)
940 except Exception, e:
941 self.logger.warn('%s: %s' %(ifaceobj.name, str(e)))
942 pass
943
944 try:
945 self._del_vrf_rules(ifaceobj.name, vrf_table)
946 except Exception, e:
947 self.logger.info('%s: %s' %(ifaceobj.name, str(e)))
948 pass
949
950 ifindex = self.ipcmd.link_get_ifindex(ifaceobj.name)
951
952 if ifindex:
953 try:
954 self.ipcmd.link_delete(ifaceobj.name)
955 except Exception, e:
956 self.logger.info('%s: %s' %(ifaceobj.name, str(e)))
957 pass
958
959 self._close_sockets(ifaceobj, ifindex)
960
961 try:
962 self._iproute2_vrf_table_entry_del(vrf_table)
963 except Exception, e:
964 self.logger.info('%s: %s' %(ifaceobj.name, str(e)))
965 pass
966
967
968 def _down_vrf_slave(self, ifacename, ifaceobj=None, vrfname=None):
969 try:
970 self._handle_existing_connections(ifaceobj, vrfname)
971 self.ipcmd.link_set(ifacename, 'nomaster')
972 # Down this slave only if it is a slave ifupdown2 manages.
973 # we dont want to down slaves that maybe up'ed by
974 # somebody else. One such example is a macvlan device
975 # which ifupdown2 addressvirtual addon module auto creates
976 if ifaceobj:
977 netlink.link_set_updown(ifacename, "down")
978 except Exception, e:
979 self.logger.warn('%s: %s' %(ifacename, str(e)))
980
981 def _down(self, ifaceobj, ifaceobj_getfunc=None):
982 try:
983 vrf_table = ifaceobj.get_attr_value_first('vrf-table')
984 if vrf_table:
985 self._iproute2_vrf_map_initialize()
986 self._down_vrf_dev(ifaceobj, vrf_table, ifaceobj_getfunc)
987 else:
988 vrf = ifaceobj.get_attr_value_first('vrf')
989 if vrf:
990 self._iproute2_vrf_map_initialize()
991 self._down_vrf_slave(ifaceobj.name, ifaceobj, None)
992 except Exception, e:
993 self.log_warn(str(e))
994
995 def _query_check_vrf_slave(self, ifaceobj, ifaceobjcurr, vrf):
996 try:
997 master = self.ipcmd.link_get_master(ifaceobj.name)
998 if not master or master != vrf:
999 ifaceobjcurr.update_config_with_status('vrf', str(master), 1)
1000 else:
1001 ifaceobjcurr.update_config_with_status('vrf', master, 0)
1002 except Exception, e:
1003 self.log_error(str(e), ifaceobjcurr)
1004
1005 def _query_check_vrf_dev(self, ifaceobj, ifaceobjcurr, vrf_table):
1006 try:
1007 if not self.ipcmd.link_exists(ifaceobj.name):
1008 self.logger.info('%s: vrf: does not exist' %(ifaceobj.name))
1009 return
1010 if vrf_table == 'auto':
1011 config_table = self._get_iproute2_vrf_table(ifaceobj.name)
1012 else:
1013 config_table = vrf_table
1014 vrfdev_attrs = self.ipcmd.link_get_linkinfo_attrs(ifaceobj.name)
1015 if not vrfdev_attrs:
1016 ifaceobjcurr.update_config_with_status('vrf-table', 'None', 1)
1017 return
1018 running_table = vrfdev_attrs.get('table')
1019 if not running_table:
1020 ifaceobjcurr.update_config_with_status('vrf-table', 'None', 1)
1021 return
1022 if config_table != running_table:
1023 ifaceobjcurr.update_config_with_status('vrf-table',
1024 running_table, 1)
1025 else:
1026 ifaceobjcurr.update_config_with_status('vrf-table',
1027 running_table, 0)
1028 if not ifupdownflags.flags.WITHDEFAULTS:
1029 return
1030 if self.vrf_helper:
1031 try:
1032 utils.exec_command('%s verify %s %s'
1033 %(self.vrf_helper,
1034 ifaceobj.name, config_table))
1035 ifaceobjcurr.update_config_with_status('vrf-helper',
1036 '%s create %s %s'
1037 %(self.vrf_helper,
1038 ifaceobj.name,
1039 config_table), 0)
1040 except Exception, e:
1041 ifaceobjcurr.update_config_with_status('vrf-helper',
1042 '%s create %s %s'
1043 %(self.vrf_helper,
1044 ifaceobj.name,
1045 config_table), 1)
1046 pass
1047 except Exception, e:
1048 self.log_warn(str(e))
1049
1050 def _query_check(self, ifaceobj, ifaceobjcurr):
1051 try:
1052 vrf_table = ifaceobj.get_attr_value_first('vrf-table')
1053 if vrf_table:
1054 self._iproute2_vrf_map_initialize(writetodisk=False)
1055 self._query_check_vrf_dev(ifaceobj, ifaceobjcurr, vrf_table)
1056 else:
1057 vrf = ifaceobj.get_attr_value_first('vrf')
1058 if vrf:
1059 self._iproute2_vrf_map_initialize(writetodisk=False)
1060 self._query_check_vrf_slave(ifaceobj, ifaceobjcurr, vrf)
1061 except Exception, e:
1062 self.log_warn(str(e))
1063
1064 def _query_running(self, ifaceobjrunning, ifaceobj_getfunc=None):
1065 try:
1066 kind = self.ipcmd.link_get_kind(ifaceobjrunning.name)
1067 if kind == 'vrf':
1068 vrfdev_attrs = self.ipcmd.link_get_linkinfo_attrs(ifaceobjrunning.name)
1069 if vrfdev_attrs:
1070 running_table = vrfdev_attrs.get('table')
1071 if running_table:
1072 ifaceobjrunning.update_config('vrf-table',
1073 running_table)
1074 return
1075 slave_kind = self.ipcmd.link_get_slave_kind(ifaceobjrunning.name)
1076 if slave_kind == 'vrf_slave':
1077 vrf = self.ipcmd.link_get_master(ifaceobjrunning.name)
1078 if vrf:
1079 ifaceobjrunning.update_config('vrf', vrf)
1080 except Exception, e:
1081 self.log_warn(str(e))
1082
1083 def _query(self, ifaceobj, **kwargs):
1084 if not self.vrf_helper:
1085 return
1086 if (ifaceobj.link_kind & ifaceLinkKind.VRF):
1087 ifaceobj.update_config('vrf-helper', '%s %s' %(self.vrf_helper,
1088 ifaceobj.name))
1089
1090 _run_ops = {'pre-up' : _up,
1091 'post-down' : _down,
1092 'query-running' : _query_running,
1093 'query-checkcurr' : _query_check,
1094 'query' : _query}
1095
1096 def get_ops(self):
1097 """ returns list of ops supported by this module """
1098 return self._run_ops.keys()
1099
1100 def _init_command_handlers(self):
1101 if not self.ipcmd:
1102 self.ipcmd = self.bondcmd = LinkUtils()
1103 if not self.dhclientcmd:
1104 self.dhclientcmd = dhclient()
1105
1106 def run(self, ifaceobj, operation, query_ifaceobj=None,
1107 ifaceobj_getfunc=None, **extra_args):
1108 """ run bond configuration on the interface object passed as argument
1109
1110 Args:
1111 **ifaceobj** (object): iface object
1112
1113 **operation** (str): any of 'pre-up', 'post-down', 'query-checkcurr',
1114 'query-running'
1115
1116 Kwargs:
1117 **query_ifaceobj** (object): query check ifaceobject. This is only
1118 valid when op is 'query-checkcurr'. It is an object same as
1119 ifaceobj, but contains running attribute values and its config
1120 status. The modules can use it to return queried running state
1121 of interfaces. status is success if the running state is same
1122 as user required state in ifaceobj. error otherwise.
1123 """
1124 op_handler = self._run_ops.get(operation)
1125 if not op_handler:
1126 return
1127 self._init_command_handlers()
1128 if operation == 'query-checkcurr':
1129 op_handler(self, ifaceobj, query_ifaceobj)
1130 else:
1131 op_handler(self, ifaceobj, ifaceobj_getfunc=ifaceobj_getfunc)