1
0
mirror of https://github.com/CumulusNetworks/ifupdown2.git synced 2024-05-06 15:54:50 +00:00
Sam Tannous 0a3bee28ca Don't allow IP addresses on ports enslaved in bonds or bridges
Ticket: CM-5146
Reviewed By: roopa,jtoppins
Testing Done: built new ifupdown package and ran testifupdown2 suite of tests

This patch prevents enslaved interfaces from having IP addresses.
(cherry picked from commit 0c00606fbc76db11557a8e946310e93a2b376aa7)
(cherry picked from commit dc30987acfc6af356b9e055db95d94ae45f0de9f)
2015-06-04 15:28:57 -04:00

594 lines
20 KiB
Python

#!/usr/bin/python
#
# Copyright 2014 Cumulus Networks, Inc. All rights reserved.
# Author: Roopa Prabhu, roopa@cumulusnetworks.com
#
# iface --
# interface object
#
"""ifupdown2 network interface object
It is modeled based on the 'iface' section in /etc/network/interfaces
file. But can be extended to include any other network interface format
"""
from collections import OrderedDict
import logging
import json
class ifaceType():
UNKNOWN = 0x0
IFACE = 0x1
BRIDGE_VLAN = 0x2
class ifaceRole():
""" ifaceRole is used to classify the ifaceobj.role of
MASTER or SLAVE where there is a bond or bridge
with bond-slaves or bridge-ports. A bond in a bridge
is both a master and slave (0x3)
"""
UNKNOWN = 0x0
SLAVE = 0x1
MASTER = 0x2
class ifaceLinkKind():
""" ifaceLlinkKind is used to identify interfaces
in the ifaceobj.link_kind attribute. Dependents of the bridge or
bond have an ifaceobj.role attribute of SLAVE and the bridge or
bond itself has ifaceobj.role of MASTER.
"""
UNKNOWN = 0x0
BRIDGE = 0x1
BOND = 0x2
VLAN = 0x4
VXLAN = 0x8
class ifaceLinkType():
LINK_UNKNOWN = 0x0
LINK_SLAVE = 0x1
LINK_MASTER = 0x2
LINK_NA = 0x3
class ifaceDependencyType():
""" Indicates type of dependency.
This class enumerates types of dependency relationships
between interfaces.
iface dependency relationships can be classified
into:
- link
- master/slave
In a 'link' dependency relationship, dependency can be shared
between interfaces. example: swp1.100 and
swp1.200 can both have 'link' swp1. swp1 is also a dependency
of swp1.100 and swp1.200. As you can see dependency
swp1 is shared between swp1.100 and swp1.200.
In a master/slave relationship like bridge and
its ports: eg: bridge br0 and its ports swp1 and swp2.
dependency swp1 and swp2 cannot be shared with any other
interface with the same dependency relationship.
ie, swp1 and swp2 cannot be in a slave relationship
with another interface. Understanding the dependency type is
required for any semantic checks between dependencies.
"""
UNKNOWN = 0x0
LINK = 0x1
MASTER_SLAVE = 0x2
class ifaceStatus():
"""Enumerates iface status """
UNKNOWN = 0x1
SUCCESS = 0x2
ERROR = 0x3
NOTFOUND = 0x4
@classmethod
def to_str(cls, state):
if state == cls.UNKNOWN:
return 'unknown'
elif state == cls.SUCCESS:
return 'success'
elif state == cls.ERROR:
return 'error'
elif state == cls.NOTFOUND:
return 'notfound'
@classmethod
def from_str(cls, state_str):
if state_str == 'unknown':
return cls.UNKNOWN
elif state_str == 'success':
return cls.SUCCESS
elif state_str == 'error':
return cls.ERROR
class ifaceState():
"""Enumerates iface state """
UNKNOWN = 0x1
NEW = 0x2
PRE_UP = 0x3
UP = 0x4
POST_UP = 0x5
PRE_DOWN = 0x6
DOWN = 0x7
POST_DOWN = 0x8
# Pseudo states
QUERY_CHECKCURR = 0x9
QUERY_RUNNING = 0xa
@classmethod
def to_str(cls, state):
if state == cls.UNKNOWN:
return 'unknown'
elif state == cls.NEW:
return 'new'
elif state == cls.PRE_UP:
return 'pre-up'
elif state == cls.UP:
return 'up'
elif state == cls.POST_UP:
return 'post-up'
elif state == cls.PRE_DOWN:
return 'pre-down'
elif state == cls.DOWN:
return 'down'
elif state == cls.POST_DOWN:
return 'post-down'
elif state == cls.QUERY_CHECKCURR:
return 'query-checkcurr'
elif state == cls.QUERY_RUNNING:
return 'query-running'
@classmethod
def from_str(cls, state_str):
if state_str == 'unknown':
return cls.UNKNOWN
elif state_str == 'new':
return cls.NEW
elif state_str == 'pre-up':
return cls.PRE_UP
elif state_str == 'up':
return cls.UP
elif state_str == 'post-up':
return cls.POST_UP
elif state_str == 'pre-down':
return cls.PRE_DOWN
elif state_str == 'down':
return cls.DOWN
elif state_str == 'post-down':
return cls.POST_DOWN
elif state_str == 'query-checkcurr':
return cls.QUERY_CHECKCURR
elif state_str == 'query-running':
return cls.QUERY_RUNNING
class ifaceJsonEncoder(json.JSONEncoder):
def default(self, o):
retconfig = {}
if o.config:
retconfig = dict((k, (v[0] if len(v) == 1 else v))
for k,v in o.config.items())
return OrderedDict({'name' : o.name,
'addr_method' : o.addr_method,
'addr_family' : o.addr_family,
'auto' : o.auto,
'config' : retconfig})
class ifaceJsonDecoder():
@classmethod
def json_to_ifaceobj(cls, ifaceattrdict):
ifaceattrdict['config'] = OrderedDict([(k, (v if isinstance(v, list)
else [v]))
for k,v in ifaceattrdict.get('config',
OrderedDict()).items()])
return iface(attrsdict=ifaceattrdict)
class iface():
""" ifupdown2 iface object class
Attributes:
**name** Name of the interface
**addr_family** Address family eg, inet, inet6. Can be None to
indicate both address families
**addr_method** Address method eg, static, manual or None for
static address method
**config** dictionary of config lines for this interface
**state** Configuration state of an interface as defined by
ifaceState
**status** Configuration status of an interface as defined by
ifaceStatus
**flags** Internal flags used by iface processing
**priv_flags** private flags owned by module using this class
**module_flags** module flags owned by module using this class
**refcnt** reference count, indicating number of interfaces
dependent on this iface
**lowerifaces** list of interface names lower to this interface or
this interface depends on
**upperifaces** list of interface names upper to this interface or
the interfaces that depend on this interface
**auto** True if interface belongs to the auto class
**classes** List of classes the interface belongs to
**env** shell environment the interface needs during
execution
**raw_config** raw interface config from file
"""
# flag to indicate that the object was created from pickled state
_PICKLED = 0x00000001
HAS_SIBLINGS = 0x00000010
IFACERANGE_ENTRY = 0x00000100
IFACERANGE_START = 0x00001000
version = '0.1'
def __init__(self, attrsdict={}):
self._set_attrs_from_dict(attrsdict)
self._config_status = {}
"""dict with config status of iface attributes"""
self.state = ifaceState.NEW
"""iface state (of type ifaceState) """
self.status = ifaceStatus.UNKNOWN
"""iface status (of type ifaceStatus) """
self.status_str = None
"""iface status str (string representing the status) """
self.flags = 0x0
"""iface flags """
self.priv_flags = 0x0
"""iface module flags dictionary with module name: flags"""
self.module_flags = {}
"""iface priv flags. can be used by the external object manager """
self.refcnt = 0
"""iface refcnt (incremented for each dependent this interface has) """
self.lowerifaces = None
"""lower iface list (in other words: slaves of this interface """
self.upperifaces = None
"""upper iface list (in other words: master of this interface """
self.classes = []
"""interface classes this iface belongs to """
self.env = None
"""environment variable dict required for this interface to run"""
self.raw_config = []
"""interface config/attributes in raw format (eg: as it appeared in the interfaces file)"""
self.linkstate = None
"""linkstate of the interface"""
self.type = ifaceType.UNKNOWN
"""interface type"""
self.priv_data = None
self.role = ifaceRole.UNKNOWN
self.realname = None
self.link_type = ifaceLinkType.LINK_UNKNOWN
self.link_kind = ifaceLinkKind.UNKNOWN
# The below attribute is used to disambiguate between various
# types of dependencies
self.dependency_type = ifaceDependencyType.UNKNOWN
def _set_attrs_from_dict(self, attrdict):
self.auto = attrdict.get('auto', False)
self.name = attrdict.get('name')
self.addr_family = attrdict.get('addr_family')
self.addr_method = attrdict.get('addr_method')
self.config = attrdict.get('config', OrderedDict())
def inc_refcnt(self):
""" increment refcnt of the interface. Usually used to indicate that
it has dependents """
self.refcnt += 1
def dec_refcnt(self):
""" decrement refcnt of the interface. Usually used to indicate that
it has lost its dependent """
self.refcnt -= 1
def is_config_present(self):
""" returns true if the interface has user provided config,
false otherwise """
addr_method = self.addr_method
if addr_method and addr_method in ['dhcp', 'dhcp6', 'loopback']:
return True
if not self.config:
return False
else:
return True
def set_class(self, classname):
""" appends class to the interfaces class list """
self.classes.append(classname)
def set_state_n_status(self, state, status):
""" sets state and status of an interface """
self.state = state
self.status = status
def set_flag(self, flag):
self.flags |= flag
def clear_flag(self, flag):
self.flags &= ~flag
def add_to_upperifaces(self, upperifacename):
""" add to the list of upperifaces """
if self.upperifaces:
if upperifacename not in self.upperifaces:
self.upperifaces.append(upperifacename)
else:
self.upperifaces = [upperifacename]
def get_attr_value(self, attr_name):
""" add to the list of upperifaces """
return self.config.get(attr_name)
def get_attr_value_first(self, attr_name):
""" get first value of the specified attr name """
attr_value_list = self.config.get(attr_name)
if attr_value_list:
return attr_value_list[0]
return None
def get_attrs_value_first(self, attrs):
""" get first value of the first attr in the list.
Useful when you have multiple attrs representing the
same thing.
"""
for attr in attrs:
attr_value_list = self.config.get(attr)
if attr_value_list:
return attr_value_list[0]
return None
def get_attr_value_n(self, attr_name, attr_index):
""" get n'th value of the specified attr name """
attr_value_list = self.config.get(attr_name)
if attr_value_list:
try:
return attr_value_list[attr_index]
except:
return None
return None
@property
def get_env(self):
""" get shell environment variables the interface must execute in """
if not self.env:
self.generate_env()
return self.env
def generate_env(self):
""" generate shell environment variables dict interface must execute
in. This is used to support legacy ifupdown scripts
"""
env = {}
config = self.config
env['IFACE'] = self.name
for attr, attr_value in config.items():
attr_env_name = 'IF_%s' %attr.upper()
env[attr_env_name] = attr_value[0]
if env:
self.env = env
def update_config(self, attr_name, attr_value):
""" add attribute name and value to the interface config """
self.config.setdefault(attr_name, []).append(attr_value)
def replace_config(self, attr_name, attr_value):
""" add attribute name and value to the interface config """
self.config[attr_name] = [attr_value]
def delete_config(self, attr_name):
""" add attribute name and value to the interface config """
try:
del self.config[attr_name]
except:
pass
def update_config_dict(self, attrdict):
self.config.update(attrdict)
def update_config_with_status(self, attr_name, attr_value, attr_status=0):
""" add attribute name and value to the interface config and also
update the config_status dict with status of this attribute config """
if not attr_value:
attr_value = ''
self.config.setdefault(attr_name, []).append(attr_value)
self._config_status.setdefault(attr_name, []).append(attr_status)
# set global iface state
if attr_status == 1:
self.status = ifaceStatus.ERROR
elif self.status != ifaceStatus.ERROR:
# Not already error, mark success
self.status = ifaceStatus.SUCCESS
def check_n_update_config_with_status_many(self, ifaceobjorig, attr_names,
attr_status=0):
# set multiple attribute status to zero
# also updates status only if the attribute is present
for attr_name in attr_names:
if not ifaceobjorig.get_attr_value_first(attr_name):
continue
self.config.setdefault(attr_name, []).append('')
self._config_status.setdefault(attr_name, []).append(attr_status)
def get_config_attr_status(self, attr_name, idx=0):
""" get status of a attribute config on this interface.
Looks at the iface _config_status dict"""
return self._config_status.get(attr_name, [])[idx]
def compare(self, dstiface):
""" compares iface object with iface object passed as argument
Returns True if object self is same as dstiface and False otherwise """
if self.name != dstiface.name: return False
if self.type != dstiface.type: return False
if self.addr_family != dstiface.addr_family: return False
if self.addr_method != dstiface.addr_method: return False
if self.auto != dstiface.auto: return False
if self.classes != dstiface.classes: return False
if len(self.config) != len(dstiface.config):
return False
if any(True for k in self.config if k not in dstiface.config):
return False
if any(True for k,v in self.config.items()
if v != dstiface.config.get(k)): return False
return True
def __getstate__(self):
odict = self.__dict__.copy()
del odict['state']
del odict['status']
del odict['lowerifaces']
del odict['upperifaces']
del odict['refcnt']
del odict['_config_status']
del odict['flags']
del odict['priv_flags']
del odict['module_flags']
del odict['raw_config']
del odict['linkstate']
del odict['env']
del odict['link_type']
del odict['link_kind']
del odict['role']
del odict['dependency_type']
return odict
def __setstate__(self, dict):
self.__dict__.update(dict)
self._config_status = {}
self.state = ifaceState.NEW
self.status = ifaceStatus.UNKNOWN
self.refcnt = 0
self.flags = 0
self.lowerifaces = None
self.upperifaces = None
self.linkstate = None
self.env = None
self.role = ifaceRole.UNKNOWN
self.priv_flags = 0
self.module_flags = {}
self.raw_config = []
self.flags |= self._PICKLED
self.link_type = ifaceLinkType.LINK_NA
self.link_kind = ifaceLinkKind.UNKNOWN
self.dependency_type = ifaceDependencyType.UNKNOWN
def dump_raw(self, logger):
indent = ' '
if self.auto:
print 'auto %s' %self.name
print (self.raw_config[0])
for i in range(1, len(self.raw_config)):
print(indent + self.raw_config[i])
def dump(self, logger):
indent = '\t'
logger.info(self.name + ' : {')
logger.info(indent + 'family: %s' %self.addr_family)
logger.info(indent + 'method: %s' %self.addr_method)
logger.info(indent + 'flags: %x' %self.flags)
logger.info(indent + 'state: %s'
%ifaceState.to_str(self.state))
logger.info(indent + 'status: %s'
%ifaceStatus.to_str(self.status))
logger.info(indent + 'refcnt: %d' %self.refcnt)
d = self.lowerifaces
if d:
logger.info(indent + 'lowerdevs: %s' %str(d))
else:
logger.info(indent + 'lowerdevs: None')
logger.info(indent + 'config: ')
config = self.config
if config:
logger.info(indent + indent + str(config))
logger.info('}')
def dump_pretty(self, with_status=False,
successstr='success', errorstr='error',
unknownstr='unknown', use_realname=False):
indent = '\t'
outbuf = ''
if use_realname and self.realname:
name = '%s' %self.realname
else:
name = '%s' %self.name
if self.auto:
outbuf += 'auto %s\n' %name
ifaceline = ''
if self.type == ifaceType.BRIDGE_VLAN:
ifaceline += 'vlan %s' %name
else:
ifaceline += 'iface %s' %name
if self.addr_family:
ifaceline += ' %s' %self.addr_family
if self.addr_method:
ifaceline += ' %s' %self.addr_method
if with_status:
status_str = None
if (self.status == ifaceStatus.ERROR or
self.status == ifaceStatus.NOTFOUND):
if self.status_str:
ifaceline += ' (%s)' %self.status_str
status_str = errorstr
elif self.status == ifaceStatus.SUCCESS:
status_str = successstr
if status_str:
outbuf += '{0:65} {1:>8}'.format(ifaceline, status_str) + '\n'
else:
outbuf += ifaceline + '\n'
if self.status == ifaceStatus.NOTFOUND:
outbuf = (outbuf.encode('utf8')
if isinstance(outbuf, unicode) else outbuf)
print outbuf + '\n'
return
else:
outbuf += ifaceline + '\n'
config = self.config
if config:
for cname, cvaluelist in config.items():
idx = 0
for cv in cvaluelist:
if with_status:
s = self.get_config_attr_status(cname, idx)
if s == -1:
status_str = unknownstr
elif s == 1:
status_str = errorstr
elif s == 0:
status_str = successstr
outbuf += (indent + '{0:55} {1:>10}'.format(
'%s %s' %(cname, cv), status_str)) + '\n'
else:
outbuf += indent + '%s %s\n' %(cname, cv)
idx += 1
if with_status:
outbuf = (outbuf.encode('utf8')
if isinstance(outbuf, unicode) else outbuf)
print outbuf