]> git.proxmox.com Git - mirror_ifupdown2.git/blob - ifupdown2/ifupdown/iface.py
Don't allow IP addresses on ports enslaved in bonds or bridges
[mirror_ifupdown2.git] / ifupdown2 / ifupdown / iface.py
1 #!/usr/bin/python
2 #
3 # Copyright 2014 Cumulus Networks, Inc. All rights reserved.
4 # Author: Roopa Prabhu, roopa@cumulusnetworks.com
5 #
6 # iface --
7 # interface object
8 #
9
10 """ifupdown2 network interface object
11
12 It is modeled based on the 'iface' section in /etc/network/interfaces
13 file. But can be extended to include any other network interface format
14 """
15
16 from collections import OrderedDict
17 import logging
18 import json
19
20 class ifaceType():
21 UNKNOWN = 0x0
22 IFACE = 0x1
23 BRIDGE_VLAN = 0x2
24
25
26 class ifaceRole():
27 """ ifaceRole is used to classify the ifaceobj.role of
28 MASTER or SLAVE where there is a bond or bridge
29 with bond-slaves or bridge-ports. A bond in a bridge
30 is both a master and slave (0x3)
31 """
32 UNKNOWN = 0x0
33 SLAVE = 0x1
34 MASTER = 0x2
35
36 class ifaceLinkKind():
37 """ ifaceLlinkKind is used to identify interfaces
38 in the ifaceobj.link_kind attribute. Dependents of the bridge or
39 bond have an ifaceobj.role attribute of SLAVE and the bridge or
40 bond itself has ifaceobj.role of MASTER.
41 """
42 UNKNOWN = 0x0
43 BRIDGE = 0x1
44 BOND = 0x2
45 VLAN = 0x4
46 VXLAN = 0x8
47
48 class ifaceLinkType():
49 LINK_UNKNOWN = 0x0
50 LINK_SLAVE = 0x1
51 LINK_MASTER = 0x2
52 LINK_NA = 0x3
53
54 class ifaceDependencyType():
55 """ Indicates type of dependency.
56
57 This class enumerates types of dependency relationships
58 between interfaces.
59
60 iface dependency relationships can be classified
61 into:
62 - link
63 - master/slave
64
65 In a 'link' dependency relationship, dependency can be shared
66 between interfaces. example: swp1.100 and
67 swp1.200 can both have 'link' swp1. swp1 is also a dependency
68 of swp1.100 and swp1.200. As you can see dependency
69 swp1 is shared between swp1.100 and swp1.200.
70
71 In a master/slave relationship like bridge and
72 its ports: eg: bridge br0 and its ports swp1 and swp2.
73 dependency swp1 and swp2 cannot be shared with any other
74 interface with the same dependency relationship.
75 ie, swp1 and swp2 cannot be in a slave relationship
76 with another interface. Understanding the dependency type is
77 required for any semantic checks between dependencies.
78
79 """
80 UNKNOWN = 0x0
81 LINK = 0x1
82 MASTER_SLAVE = 0x2
83
84 class ifaceStatus():
85 """Enumerates iface status """
86
87 UNKNOWN = 0x1
88 SUCCESS = 0x2
89 ERROR = 0x3
90 NOTFOUND = 0x4
91
92 @classmethod
93 def to_str(cls, state):
94 if state == cls.UNKNOWN:
95 return 'unknown'
96 elif state == cls.SUCCESS:
97 return 'success'
98 elif state == cls.ERROR:
99 return 'error'
100 elif state == cls.NOTFOUND:
101 return 'notfound'
102
103 @classmethod
104 def from_str(cls, state_str):
105 if state_str == 'unknown':
106 return cls.UNKNOWN
107 elif state_str == 'success':
108 return cls.SUCCESS
109 elif state_str == 'error':
110 return cls.ERROR
111
112 class ifaceState():
113 """Enumerates iface state """
114
115 UNKNOWN = 0x1
116 NEW = 0x2
117 PRE_UP = 0x3
118 UP = 0x4
119 POST_UP = 0x5
120 PRE_DOWN = 0x6
121 DOWN = 0x7
122 POST_DOWN = 0x8
123
124 # Pseudo states
125 QUERY_CHECKCURR = 0x9
126 QUERY_RUNNING = 0xa
127
128 @classmethod
129 def to_str(cls, state):
130 if state == cls.UNKNOWN:
131 return 'unknown'
132 elif state == cls.NEW:
133 return 'new'
134 elif state == cls.PRE_UP:
135 return 'pre-up'
136 elif state == cls.UP:
137 return 'up'
138 elif state == cls.POST_UP:
139 return 'post-up'
140 elif state == cls.PRE_DOWN:
141 return 'pre-down'
142 elif state == cls.DOWN:
143 return 'down'
144 elif state == cls.POST_DOWN:
145 return 'post-down'
146 elif state == cls.QUERY_CHECKCURR:
147 return 'query-checkcurr'
148 elif state == cls.QUERY_RUNNING:
149 return 'query-running'
150
151 @classmethod
152 def from_str(cls, state_str):
153 if state_str == 'unknown':
154 return cls.UNKNOWN
155 elif state_str == 'new':
156 return cls.NEW
157 elif state_str == 'pre-up':
158 return cls.PRE_UP
159 elif state_str == 'up':
160 return cls.UP
161 elif state_str == 'post-up':
162 return cls.POST_UP
163 elif state_str == 'pre-down':
164 return cls.PRE_DOWN
165 elif state_str == 'down':
166 return cls.DOWN
167 elif state_str == 'post-down':
168 return cls.POST_DOWN
169 elif state_str == 'query-checkcurr':
170 return cls.QUERY_CHECKCURR
171 elif state_str == 'query-running':
172 return cls.QUERY_RUNNING
173
174 class ifaceJsonEncoder(json.JSONEncoder):
175 def default(self, o):
176 retconfig = {}
177 if o.config:
178 retconfig = dict((k, (v[0] if len(v) == 1 else v))
179 for k,v in o.config.items())
180 return OrderedDict({'name' : o.name,
181 'addr_method' : o.addr_method,
182 'addr_family' : o.addr_family,
183 'auto' : o.auto,
184 'config' : retconfig})
185
186 class ifaceJsonDecoder():
187 @classmethod
188 def json_to_ifaceobj(cls, ifaceattrdict):
189 ifaceattrdict['config'] = OrderedDict([(k, (v if isinstance(v, list)
190 else [v]))
191 for k,v in ifaceattrdict.get('config',
192 OrderedDict()).items()])
193 return iface(attrsdict=ifaceattrdict)
194
195 class iface():
196 """ ifupdown2 iface object class
197
198 Attributes:
199 **name** Name of the interface
200
201 **addr_family** Address family eg, inet, inet6. Can be None to
202 indicate both address families
203
204 **addr_method** Address method eg, static, manual or None for
205 static address method
206
207 **config** dictionary of config lines for this interface
208
209 **state** Configuration state of an interface as defined by
210 ifaceState
211
212 **status** Configuration status of an interface as defined by
213 ifaceStatus
214
215 **flags** Internal flags used by iface processing
216
217 **priv_flags** private flags owned by module using this class
218
219 **module_flags** module flags owned by module using this class
220
221 **refcnt** reference count, indicating number of interfaces
222 dependent on this iface
223
224 **lowerifaces** list of interface names lower to this interface or
225 this interface depends on
226
227 **upperifaces** list of interface names upper to this interface or
228 the interfaces that depend on this interface
229
230 **auto** True if interface belongs to the auto class
231
232 **classes** List of classes the interface belongs to
233
234 **env** shell environment the interface needs during
235 execution
236
237 **raw_config** raw interface config from file
238 """
239
240 # flag to indicate that the object was created from pickled state
241 _PICKLED = 0x00000001
242 HAS_SIBLINGS = 0x00000010
243 IFACERANGE_ENTRY = 0x00000100
244 IFACERANGE_START = 0x00001000
245
246 version = '0.1'
247
248 def __init__(self, attrsdict={}):
249 self._set_attrs_from_dict(attrsdict)
250 self._config_status = {}
251 """dict with config status of iface attributes"""
252 self.state = ifaceState.NEW
253 """iface state (of type ifaceState) """
254 self.status = ifaceStatus.UNKNOWN
255 """iface status (of type ifaceStatus) """
256 self.status_str = None
257 """iface status str (string representing the status) """
258 self.flags = 0x0
259 """iface flags """
260 self.priv_flags = 0x0
261 """iface module flags dictionary with module name: flags"""
262 self.module_flags = {}
263 """iface priv flags. can be used by the external object manager """
264 self.refcnt = 0
265 """iface refcnt (incremented for each dependent this interface has) """
266 self.lowerifaces = None
267 """lower iface list (in other words: slaves of this interface """
268 self.upperifaces = None
269 """upper iface list (in other words: master of this interface """
270 self.classes = []
271 """interface classes this iface belongs to """
272 self.env = None
273 """environment variable dict required for this interface to run"""
274 self.raw_config = []
275 """interface config/attributes in raw format (eg: as it appeared in the interfaces file)"""
276 self.linkstate = None
277 """linkstate of the interface"""
278 self.type = ifaceType.UNKNOWN
279 """interface type"""
280 self.priv_data = None
281 self.role = ifaceRole.UNKNOWN
282 self.realname = None
283 self.link_type = ifaceLinkType.LINK_UNKNOWN
284 self.link_kind = ifaceLinkKind.UNKNOWN
285
286 # The below attribute is used to disambiguate between various
287 # types of dependencies
288 self.dependency_type = ifaceDependencyType.UNKNOWN
289
290 def _set_attrs_from_dict(self, attrdict):
291 self.auto = attrdict.get('auto', False)
292 self.name = attrdict.get('name')
293 self.addr_family = attrdict.get('addr_family')
294 self.addr_method = attrdict.get('addr_method')
295 self.config = attrdict.get('config', OrderedDict())
296
297 def inc_refcnt(self):
298 """ increment refcnt of the interface. Usually used to indicate that
299 it has dependents """
300 self.refcnt += 1
301
302 def dec_refcnt(self):
303 """ decrement refcnt of the interface. Usually used to indicate that
304 it has lost its dependent """
305 self.refcnt -= 1
306
307 def is_config_present(self):
308 """ returns true if the interface has user provided config,
309 false otherwise """
310 addr_method = self.addr_method
311 if addr_method and addr_method in ['dhcp', 'dhcp6', 'loopback']:
312 return True
313 if not self.config:
314 return False
315 else:
316 return True
317
318 def set_class(self, classname):
319 """ appends class to the interfaces class list """
320 self.classes.append(classname)
321
322 def set_state_n_status(self, state, status):
323 """ sets state and status of an interface """
324 self.state = state
325 self.status = status
326
327 def set_flag(self, flag):
328 self.flags |= flag
329
330 def clear_flag(self, flag):
331 self.flags &= ~flag
332
333 def add_to_upperifaces(self, upperifacename):
334 """ add to the list of upperifaces """
335 if self.upperifaces:
336 if upperifacename not in self.upperifaces:
337 self.upperifaces.append(upperifacename)
338 else:
339 self.upperifaces = [upperifacename]
340
341 def get_attr_value(self, attr_name):
342 """ add to the list of upperifaces """
343 return self.config.get(attr_name)
344
345 def get_attr_value_first(self, attr_name):
346 """ get first value of the specified attr name """
347 attr_value_list = self.config.get(attr_name)
348 if attr_value_list:
349 return attr_value_list[0]
350 return None
351
352 def get_attrs_value_first(self, attrs):
353 """ get first value of the first attr in the list.
354 Useful when you have multiple attrs representing the
355 same thing.
356 """
357 for attr in attrs:
358 attr_value_list = self.config.get(attr)
359 if attr_value_list:
360 return attr_value_list[0]
361 return None
362
363 def get_attr_value_n(self, attr_name, attr_index):
364 """ get n'th value of the specified attr name """
365 attr_value_list = self.config.get(attr_name)
366 if attr_value_list:
367 try:
368 return attr_value_list[attr_index]
369 except:
370 return None
371 return None
372
373 @property
374 def get_env(self):
375 """ get shell environment variables the interface must execute in """
376 if not self.env:
377 self.generate_env()
378 return self.env
379
380 def generate_env(self):
381 """ generate shell environment variables dict interface must execute
382 in. This is used to support legacy ifupdown scripts
383 """
384 env = {}
385 config = self.config
386 env['IFACE'] = self.name
387 for attr, attr_value in config.items():
388 attr_env_name = 'IF_%s' %attr.upper()
389 env[attr_env_name] = attr_value[0]
390 if env:
391 self.env = env
392
393 def update_config(self, attr_name, attr_value):
394 """ add attribute name and value to the interface config """
395 self.config.setdefault(attr_name, []).append(attr_value)
396
397 def replace_config(self, attr_name, attr_value):
398 """ add attribute name and value to the interface config """
399 self.config[attr_name] = [attr_value]
400
401 def delete_config(self, attr_name):
402 """ add attribute name and value to the interface config """
403 try:
404 del self.config[attr_name]
405 except:
406 pass
407
408 def update_config_dict(self, attrdict):
409 self.config.update(attrdict)
410
411 def update_config_with_status(self, attr_name, attr_value, attr_status=0):
412 """ add attribute name and value to the interface config and also
413 update the config_status dict with status of this attribute config """
414 if not attr_value:
415 attr_value = ''
416 self.config.setdefault(attr_name, []).append(attr_value)
417 self._config_status.setdefault(attr_name, []).append(attr_status)
418 # set global iface state
419 if attr_status == 1:
420 self.status = ifaceStatus.ERROR
421 elif self.status != ifaceStatus.ERROR:
422 # Not already error, mark success
423 self.status = ifaceStatus.SUCCESS
424
425 def check_n_update_config_with_status_many(self, ifaceobjorig, attr_names,
426 attr_status=0):
427 # set multiple attribute status to zero
428 # also updates status only if the attribute is present
429 for attr_name in attr_names:
430 if not ifaceobjorig.get_attr_value_first(attr_name):
431 continue
432 self.config.setdefault(attr_name, []).append('')
433 self._config_status.setdefault(attr_name, []).append(attr_status)
434
435 def get_config_attr_status(self, attr_name, idx=0):
436 """ get status of a attribute config on this interface.
437
438 Looks at the iface _config_status dict"""
439 return self._config_status.get(attr_name, [])[idx]
440
441 def compare(self, dstiface):
442 """ compares iface object with iface object passed as argument
443
444 Returns True if object self is same as dstiface and False otherwise """
445
446 if self.name != dstiface.name: return False
447 if self.type != dstiface.type: return False
448 if self.addr_family != dstiface.addr_family: return False
449 if self.addr_method != dstiface.addr_method: return False
450 if self.auto != dstiface.auto: return False
451 if self.classes != dstiface.classes: return False
452 if len(self.config) != len(dstiface.config):
453 return False
454 if any(True for k in self.config if k not in dstiface.config):
455 return False
456 if any(True for k,v in self.config.items()
457 if v != dstiface.config.get(k)): return False
458 return True
459
460
461 def __getstate__(self):
462 odict = self.__dict__.copy()
463 del odict['state']
464 del odict['status']
465 del odict['lowerifaces']
466 del odict['upperifaces']
467 del odict['refcnt']
468 del odict['_config_status']
469 del odict['flags']
470 del odict['priv_flags']
471 del odict['module_flags']
472 del odict['raw_config']
473 del odict['linkstate']
474 del odict['env']
475 del odict['link_type']
476 del odict['link_kind']
477 del odict['role']
478 del odict['dependency_type']
479 return odict
480
481 def __setstate__(self, dict):
482 self.__dict__.update(dict)
483 self._config_status = {}
484 self.state = ifaceState.NEW
485 self.status = ifaceStatus.UNKNOWN
486 self.refcnt = 0
487 self.flags = 0
488 self.lowerifaces = None
489 self.upperifaces = None
490 self.linkstate = None
491 self.env = None
492 self.role = ifaceRole.UNKNOWN
493 self.priv_flags = 0
494 self.module_flags = {}
495 self.raw_config = []
496 self.flags |= self._PICKLED
497 self.link_type = ifaceLinkType.LINK_NA
498 self.link_kind = ifaceLinkKind.UNKNOWN
499 self.dependency_type = ifaceDependencyType.UNKNOWN
500
501 def dump_raw(self, logger):
502 indent = ' '
503 if self.auto:
504 print 'auto %s' %self.name
505 print (self.raw_config[0])
506 for i in range(1, len(self.raw_config)):
507 print(indent + self.raw_config[i])
508
509 def dump(self, logger):
510 indent = '\t'
511 logger.info(self.name + ' : {')
512 logger.info(indent + 'family: %s' %self.addr_family)
513 logger.info(indent + 'method: %s' %self.addr_method)
514 logger.info(indent + 'flags: %x' %self.flags)
515 logger.info(indent + 'state: %s'
516 %ifaceState.to_str(self.state))
517 logger.info(indent + 'status: %s'
518 %ifaceStatus.to_str(self.status))
519 logger.info(indent + 'refcnt: %d' %self.refcnt)
520 d = self.lowerifaces
521 if d:
522 logger.info(indent + 'lowerdevs: %s' %str(d))
523 else:
524 logger.info(indent + 'lowerdevs: None')
525
526 logger.info(indent + 'config: ')
527 config = self.config
528 if config:
529 logger.info(indent + indent + str(config))
530 logger.info('}')
531
532 def dump_pretty(self, with_status=False,
533 successstr='success', errorstr='error',
534 unknownstr='unknown', use_realname=False):
535 indent = '\t'
536 outbuf = ''
537 if use_realname and self.realname:
538 name = '%s' %self.realname
539 else:
540 name = '%s' %self.name
541 if self.auto:
542 outbuf += 'auto %s\n' %name
543 ifaceline = ''
544 if self.type == ifaceType.BRIDGE_VLAN:
545 ifaceline += 'vlan %s' %name
546 else:
547 ifaceline += 'iface %s' %name
548 if self.addr_family:
549 ifaceline += ' %s' %self.addr_family
550 if self.addr_method:
551 ifaceline += ' %s' %self.addr_method
552 if with_status:
553 status_str = None
554 if (self.status == ifaceStatus.ERROR or
555 self.status == ifaceStatus.NOTFOUND):
556 if self.status_str:
557 ifaceline += ' (%s)' %self.status_str
558 status_str = errorstr
559 elif self.status == ifaceStatus.SUCCESS:
560 status_str = successstr
561 if status_str:
562 outbuf += '{0:65} {1:>8}'.format(ifaceline, status_str) + '\n'
563 else:
564 outbuf += ifaceline + '\n'
565 if self.status == ifaceStatus.NOTFOUND:
566 outbuf = (outbuf.encode('utf8')
567 if isinstance(outbuf, unicode) else outbuf)
568 print outbuf + '\n'
569 return
570 else:
571 outbuf += ifaceline + '\n'
572 config = self.config
573 if config:
574 for cname, cvaluelist in config.items():
575 idx = 0
576 for cv in cvaluelist:
577 if with_status:
578 s = self.get_config_attr_status(cname, idx)
579 if s == -1:
580 status_str = unknownstr
581 elif s == 1:
582 status_str = errorstr
583 elif s == 0:
584 status_str = successstr
585 outbuf += (indent + '{0:55} {1:>10}'.format(
586 '%s %s' %(cname, cv), status_str)) + '\n'
587 else:
588 outbuf += indent + '%s %s\n' %(cname, cv)
589 idx += 1
590 if with_status:
591 outbuf = (outbuf.encode('utf8')
592 if isinstance(outbuf, unicode) else outbuf)
593 print outbuf