1
0
mirror of https://github.com/CumulusNetworks/ifupdown2.git synced 2024-05-06 15:54:50 +00:00

move ifupdown2/* .

ifupdown2 code was one level deeper because ifupdown2 initially
had ifupdown2 and ifupdown2-addons as two separate packages.
Since they were combined into one package, it makes sense to
move all combined code under the top level directory

Signed-off-by: Roopa Prabhu <roopa@cumulusnetworks.com>
This commit is contained in:
Roopa Prabhu
2015-08-02 05:45:48 -07:00
parent 65c4a2e548
commit c9a33a72d4
94 changed files with 0 additions and 0 deletions

5
ifupdown/__init__.py Normal file
View File

@@ -0,0 +1,5 @@
""" ifupdown2 package.
.. moduleauthor:: Roopa Prabhu <roopa@cumulusnetworks.com>
"""

23
ifupdown/exceptions.py Normal file
View File

@@ -0,0 +1,23 @@
#!/usr/bin/python
#
# Copyright 2014 Cumulus Networks, Inc. All rights reserved.
# Author: Roopa Prabhu, roopa@cumulusnetworks.com
#
# ifupdown --
# exceptions
#
class Error(Exception):
"""Base class for exceptions in ifupdown"""
pass
class ifaceNotFoundError(Error):
pass
class invalidValueError(Error):
pass
class errorReadingStateError(Error):
pass

89
ifupdown/graph.py Normal file
View File

@@ -0,0 +1,89 @@
#!/usr/bin/python
#
# Copyright 2014 Cumulus Networks, Inc. All rights reserved.
# Author: Roopa Prabhu, roopa@cumulusnetworks.com
#
# graph --
# graph helper module for ifupdown
#
import logging
import copy
from collections import deque
try:
from gvgen import *
except ImportError as e:
pass
class graph():
""" graph functions to sort and print interface graph """
def __init__(self):
self.logger = logging.getLogger('ifupdown.' +
self.__class__.__name__)
@classmethod
def topological_sort_graphs_all(cls, dependency_graphs, indegrees_arg):
""" runs topological sort on interface list passed as dependency graph
Args:
**dependency_graphs** (dict): dependency graph with dependency
lists for interfaces
**indegrees_arg** (dict): indegrees array for all interfaces
"""
S = []
Q = deque()
indegrees = copy.deepcopy(indegrees_arg)
for ifname,indegree in indegrees.items():
if indegree == 0:
Q.append(ifname)
while len(Q):
# initialize queue
x = Q.popleft()
# Get dependents of x
dlist = dependency_graphs.get(x)
if not dlist:
S.append(x)
continue
for y in dlist:
indegrees[y] = indegrees.get(y) - 1
if indegrees.get(y) == 0:
Q.append(y)
S.append(x)
for ifname,indegree in indegrees.items():
if indegree != 0:
raise Exception('cycle found involving iface %s' %ifname +
' (indegree %d)' %indegree)
return S
@classmethod
def generate_dots(cls, dependency_graph, indegrees):
""" spits out interface dependency graph in dot format
Args:
**dependency_graphs** (dict): dependency graph with dependency
lists for interfaces
**indegrees_arg** (dict): indegrees array for all interfaces
"""
gvgraph = GvGen()
graphnodes = {}
for v in dependency_graph.keys():
graphnodes[v] = gvgraph.newItem(v)
for i, v in graphnodes.items():
dlist = dependency_graph.get(i, [])
if not dlist:
continue
for d in dlist:
gvgraph.newLink(v, graphnodes.get(d))
gvgraph.dot()

593
ifupdown/iface.py Normal file
View File

@@ -0,0 +1,593 @@
#!/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)

36
ifupdown/iff.py Normal file
View File

@@ -0,0 +1,36 @@
#!/usr/bin/env python
#
# Copyright 2014 Cumulus Networks, Inc. All rights reserved.
#
# Author: Scott Feldman, sfeldma@cumulusnetworks.com
#
#
# from /usr/include/linux/if.h
#
# Standard interface flags (netdevice->flags).
IFF_UP = 0x1 # interface is up
IFF_BROADCAST = 0x2 # broadcast address valid
IFF_DEBUG = 0x4 # turn on debugging
IFF_LOOPBACK = 0x8 # is a loopback net
IFF_POINTOPOINT = 0x10 # interface is has p-p link
IFF_NOTRAILERS = 0x20 # avoid use of trailers
IFF_RUNNING = 0x40 # interface RFC2863 OPER_UP
IFF_NOARP = 0x80 # no ARP protocol
IFF_PROMISC = 0x100 # receive all packets
IFF_ALLMULTI = 0x200 # receive all multicast packets
IFF_MASTER = 0x400 # master of a load balancer
IFF_SLAVE = 0x800 # slave of a load balancer
IFF_MULTICAST = 0x1000 # Supports multicast
IFF_PORTSEL = 0x2000 # can set media type
IFF_AUTOMEDIA = 0x4000 # auto media select active
IFF_DYNAMIC = 0x8000 # dialup device with changing addresses
IFF_LOWER_UP = 0x10000 # driver signals L1 up
IFF_DORMANT = 0x20000 # driver signals dormant
IFF_ECHO = 0x40000 # echo sent packets

72
ifupdown/ifupdownbase.py Normal file
View File

@@ -0,0 +1,72 @@
#!/usr/bin/python
#
# Copyright 2014 Cumulus Networks, Inc. All rights reserved.
# Author: Roopa Prabhu, roopa@cumulusnetworks.com
#
# ifupdownBase --
# base object for various ifupdown objects
#
import logging
import subprocess
import re
import os
from iface import *
import rtnetlink_api as rtnetlink_api
class ifupdownBase(object):
def __init__(self):
modulename = self.__class__.__name__
self.logger = logging.getLogger('ifupdown.' + modulename)
def exec_command(self, cmd, cmdenv=None, nowait=False):
cmd_returncode = 0
cmdout = ''
try:
self.logger.info('Executing ' + cmd)
if self.DRYRUN:
return cmdout
ch = subprocess.Popen(cmd.split(),
stdout=subprocess.PIPE,
shell=False, env=cmdenv,
stderr=subprocess.STDOUT,
close_fds=True)
cmdout = ch.communicate()[0]
cmd_returncode = ch.wait()
except OSError as e:
raise Exception('could not execute ' + cmd +
'(' + str(e) + ')')
if cmd_returncode != 0:
raise Exception('error executing cmd \'%s\'' %cmd +
'\n(' + cmdout.strip('\n ') + ')')
return cmdout
def ignore_error(self, errmsg):
if (self.FORCE == True or re.search(r'exists', errmsg,
re.IGNORECASE | re.MULTILINE) is not None):
return True
return False
def log_warn(self, str):
if self.ignore_error(str) == False:
if self.logger.getEffectiveLevel() == logging.DEBUG:
traceback.print_stack()
self.logger.warn(str)
pass
def log_error(self, str):
if self.ignore_error(str) == False:
raise
#raise Exception(str)
else:
pass
def link_exists(self, ifacename):
return os.path.exists('/sys/class/net/%s' %ifacename)
def link_up(self, ifacename):
rtnetlink_api.rtnl_api.link_set(ifacename, "up")
def link_down(self, ifacename):
rtnetlink_api.rtnl_api.link_set(ifacename, "down")

1403
ifupdown/ifupdownmain.py Normal file

File diff suppressed because it is too large Load Diff

237
ifupdown/netlink.py Normal file
View File

@@ -0,0 +1,237 @@
#!/usr/bin/env python
#
# Copyright 2014 Cumulus Networks, Inc. All rights reserved.
# Author: Scott Feldman, sfeldma@cumulusnetworks.com
#
from os import strerror
import select
from time import time
import socket
from ctypes import *
from errno import *
import logging
logger = logging.getLogger(__name__)
#
# from /usr/include/linux/netlink.h
#
NETLINK_ROUTE = 0 # Routing/device hook
NETLINK_UNUSED = 1 # Unused number
NETLINK_USERSOCK = 2 # Reserved for user mode socket protocols
NETLINK_FIREWALL = 3 # Firewalling hook
NETLINK_INET_DIAG = 4 # INET socket monitoring
NETLINK_NFLOG = 5 # netfilter/iptables ULOG
NETLINK_XFRM = 6 # ipsec
NETLINK_SELINUX = 7 # SELinux event notifications
NETLINK_ISCSI = 8 # Open-iSCSI
NETLINK_AUDIT = 9 # auditing
NETLINK_FIB_LOOKUP = 10
NETLINK_CONNECTOR = 11
NETLINK_NETFILTER = 12 # netfilter subsystem
NETLINK_IP6_FW = 13
NETLINK_DNRTMSG = 14 # DECnet routing messages
NETLINK_KOBJECT_UEVENT = 15 # Kernel messages to userspace
NETLINK_GENERIC = 16
NETLINK_SCSITRANSPORT = 18 # SCSI Transports
NETLINK_ECRYPTFS = 19
NETLINK_RDMA = 20
NETLINK_CRYPTO = 21 # Crypto layer
NLMSG_NOOP = 1 # Nothing.
NLMSG_ERROR = 2 # Error
NLMSG_DONE = 3 # End of a dump
NLMSG_OVERRUN = 4 # Data lost
NETLINK_NO_ENOBUFS = 5
SOL_NETLINK = 270
class Nlmsghdr(Structure):
_fields_ = [
('nlmsg_len', c_uint32),
('nlmsg_type', c_uint16),
('nlmsg_flags', c_uint16),
('nlmsg_seq', c_uint32),
('nlmsg_pid', c_uint32)
]
def dump(self):
print ('nlmsg_len', self.nlmsg_len)
print ('nlmsg_type', self.nlmsg_type)
print ('nlmsg_flags 0x%04x' % self.nlmsg_flags)
print ('nlmsg_seq', self.nlmsg_seq)
print ('nlmsg_pid', self.nlmsg_pid)
# Flags values
NLM_F_REQUEST = 1 # It is request message.
NLM_F_MULTI = 2 # Multipart message, terminated by NLMSG_DONE
NLM_F_ACK = 4 # Reply with ack, with zero or error code
NLM_F_ECHO = 8 # Echo this request
NLM_F_DUMP_INTR = 16 # Dump was inconsistent due to sequence change
# Modifiers to GET request
NLM_F_ROOT = 0x100 # specify tree root
NLM_F_MATCH = 0x200 # return all matching
NLM_F_ATOMIC = 0x400 # atomic GET
NLM_F_DUMP = (NLM_F_ROOT|NLM_F_MATCH)
# Modifiers to NEW request
NLM_F_REPLACE = 0x100 # Override existing
NLM_F_EXCL = 0x200 # Do not touch, if it exists
NLM_F_CREATE = 0x400 # Create, if it does not exist
NLM_F_APPEND = 0x800 # Add to end of list
NLMSG_ALIGNTO = 4
def NLMSG_ALIGN(len):
return (len + NLMSG_ALIGNTO - 1) & ~(NLMSG_ALIGNTO - 1)
def NLMSG_HDRLEN():
return NLMSG_ALIGN(sizeof(Nlmsghdr))
def NLMSG_LENGTH(len):
return len + NLMSG_ALIGN(NLMSG_HDRLEN())
def NLMSG_SPACE(len):
return NLMSG_ALIGN(NLMSG_LENGTH(len))
def NLMSG_DATA(nlh):
return addressof(nlh) + NLMSG_LENGTH(0)
def NLMSG_NEXT(nlh, len):
cur = NLMSG_ALIGN(nlh.nlmsg_len)
nlh = Nlmsghdr.from_address(addressof(nlh) + cur)
return len - cur, nlh
def NLMSG_OK(nlh, len):
return len >= sizeof(Nlmsghdr) and \
nlh.nlmsg_len >= sizeof(Nlmsghdr) and \
nlh.nlmsg_len <= len
class Nlmsgerr(Structure):
_fields_ = [
('error', c_int),
('msg', Nlmsghdr),
]
class NetlinkError(Exception):
def __init__(self, message):
Exception.__init__(self, message)
#print(message)
class Netlink(socket.socket):
def __init__(self, pid, proto):
self.pid = pid
self.recvbuf = bytearray(8 * 1024)
self.sendbuf = bytearray(8 * 1024)
self.seq = int(time())
try:
socket.socket.__init__(self, socket.AF_NETLINK, \
socket.SOCK_RAW, proto)
self.setblocking(0)
# Need to turn off ENOBUFS for netlink socket otherwise
# in a kernel overrun situation, the socket will return
# ENOBUFS on socket recv and be stuck for future recvs.
self.setsockopt(SOL_NETLINK, NETLINK_NO_ENOBUFS, 1)
except OSError as e:
raise NetlinkError("open: socket err: %s" %e.strerror)
def bind(self, groups, cb):
self._nl_cb = cb
try:
socket.socket.bind(self, (self.pid, groups))
except OSError as e:
raise NetlinkError("bind: socket err: %s" %e.strerror)
def sendall(self, string):
try:
socket.socket.sendall(self, string)
except OSError as e:
raise NetlinkError("send: socket err: %s" %e.strerror)
def _process_nlh(self, recv, nlh):
while NLMSG_OK(nlh, recv):
yield recv, nlh
recv, nlh = NLMSG_NEXT(nlh, recv)
def process(self, tokens=[]):
found_done = False
try:
recv, src_addr = self.recvfrom_into(self.recvbuf)
if not recv:
# EOF
print ("EOF")
return False
except OSError as e:
if e.errno in [EINTR, EAGAIN]:
return False
raise NetlinkError("netlink: socket err: %s" %e.strerror)
nlh = Nlmsghdr.from_buffer(self.recvbuf)
for recv, nlh in self._process_nlh(recv, nlh):
# print "type %u, seq %u, pid %u" % \
# (nlh.nlmsg_type, nlh.nlmsg_seq, nlh.nlmsg_pid)
l = nlh.nlmsg_len - sizeof(Nlmsghdr)
if l < 0 or nlh.nlmsg_len > recv:
raise NetlinkError("netlink: malformed msg: len %d" % \
nlh.nlmsg_len)
if tokens:
current = (nlh.nlmsg_pid, nlh.nlmsg_seq)
if current not in tokens:
continue
if nlh.nlmsg_type == NLMSG_DONE:
found_done = True
break
if nlh.nlmsg_type == NLMSG_ERROR:
err = Nlmsgerr.from_address(NLMSG_DATA(nlh))
if err.error == 0:
return False
raise NetlinkError("netlink: %s" % strerror(abs(err.error)))
if self._nl_cb:
self._nl_cb(nlh)
if found_done:
return False
remnant = recv - NLMSG_ALIGN(nlh.nlmsg_len) > 0
if remnant:
raise NetlinkError("netlink: remnant of size %d" % \
remnant)
return True
def process_wait(self, tokens):
while self.process(tokens):
pass
def process_forever(self):
epoll = select.epoll()
epoll.register(self.fileno(), select.EPOLLIN)
while True:
events = epoll.poll()
for fileno, event in events:
if fileno == self.fileno():
self.process()
def process_event(self, event):
return self.process()

View File

@@ -0,0 +1,437 @@
#!/usr/bin/python
#
# Copyright 2014 Cumulus Networks, Inc. All rights reserved.
# Author: Roopa Prabhu, roopa@cumulusnetworks.com
#
# networkInterfaces --
# ifupdown network interfaces file parser
#
import collections
import logging
import glob
import re
import os
import copy
from utils import utils
from iface import *
from template import templateEngine
whitespaces = '\n\t\r '
class networkInterfaces():
""" debian ifupdown /etc/network/interfaces file parser """
hotplugs = {}
auto_ifaces = []
callbacks = {}
auto_all = False
_addrfams = {'inet' : ['static', 'manual', 'loopback', 'dhcp', 'dhcp6'],
'inet6' : ['static', 'manual', 'loopback', 'dhcp', 'dhcp6']}
def __init__(self, interfacesfile='/etc/network/interfaces',
interfacesfileiobuf=None, interfacesfileformat='native',
template_engine=None, template_lookuppath=None):
"""This member function initializes the networkinterfaces parser object.
Kwargs:
**interfacesfile** (str): path to the interfaces file (default is /etc/network/interfaces)
**interfacesfileiobuf** (object): interfaces file io stream
**interfacesfileformat** (str): format of interfaces file (choices are 'native' and 'json'. 'native' being the default)
**template_engine** (str): template engine name
**template_lookuppath** (str): template lookup path
Raises:
AttributeError, KeyError """
self.logger = logging.getLogger('ifupdown.' +
self.__class__.__name__)
self.callbacks = {'iface_found' : None,
'validateifaceattr' : None,
'validateifaceobj' : None}
self.allow_classes = {}
self.interfacesfile = interfacesfile
self.interfacesfileiobuf = interfacesfileiobuf
self.interfacesfileformat = interfacesfileformat
self._filestack = [self.interfacesfile]
self._template_engine = templateEngine(template_engine,
template_lookuppath)
self._currentfile_has_template = False
self._ws_split_regex = re.compile(r'[\s\t]\s*')
@property
def _currentfile(self):
try:
return self._filestack[-1]
except:
return self.interfacesfile
def _parse_error(self, filename, lineno, msg):
if lineno == -1 or self._currentfile_has_template:
self.logger.error('%s: %s' %(filename, msg))
else:
self.logger.error('%s: line%d: %s' %(filename, lineno, msg))
def _parse_warn(self, filename, lineno, msg):
if lineno == -1 or self._currentfile_has_template:
self.logger.warn('%s: %s' %(filename, msg))
else:
self.logger.warn('%s: line%d: %s' %(filename, lineno, msg))
def _validate_addr_family(self, ifaceobj, lineno=-1):
if ifaceobj.addr_family:
if not self._addrfams.get(ifaceobj.addr_family):
self._parse_error(self._currentfile, lineno,
'iface %s: unsupported address family \'%s\''
%(ifaceobj.name, ifaceobj.addr_family))
ifaceobj.addr_family = None
ifaceobj.addr_method = None
return
if ifaceobj.addr_method:
if (ifaceobj.addr_method not in
self._addrfams.get(ifaceobj.addr_family)):
self._parse_error(self._currentfile, lineno,
'iface %s: unsupported address method \'%s\''
%(ifaceobj.name, ifaceobj.addr_method))
else:
ifaceobj.addr_method = 'static'
def subscribe(self, callback_name, callback_func):
"""This member function registers callback functions.
Args:
**callback_name** (str): callback function name (supported names: 'iface_found', 'validateifaceattr', 'validateifaceobj')
**callback_func** (function pointer): callback function pointer
Warns on error
"""
if callback_name not in self.callbacks.keys():
print ('warning: invalid callback ' + callback_name)
return -1
self.callbacks[callback_name] = callback_func
def ignore_line(self, line):
l = line.strip(whitespaces)
if not l or l[0] == '#':
return 1
return 0
def process_allow(self, lines, cur_idx, lineno):
allow_line = lines[cur_idx]
words = re.split(self._ws_split_regex, allow_line)
if len(words) <= 1:
raise Exception('invalid allow line \'%s\' at line %d'
%(allow_line, lineno))
allow_class = words[0].split('-')[1]
ifacenames = words[1:]
if self.allow_classes.get(allow_class):
for i in ifacenames:
self.allow_classes[allow_class].append(i)
else:
self.allow_classes[allow_class] = ifacenames
return 0
def process_source(self, lines, cur_idx, lineno):
# Support regex
self.logger.debug('processing sourced line ..\'%s\'' %lines[cur_idx])
sourced_file = re.split(self._ws_split_regex, lines[cur_idx], 2)[1]
if sourced_file:
filenames = glob.glob(sourced_file)
if not filenames:
self._parse_warn(self._currentfile, lineno,
'cannot find source file %s' %sourced_file)
return 0
for f in filenames:
self.read_file(f)
else:
self._parse_error(self._currentfile, lineno,
'unable to read source line')
return 0
def process_auto(self, lines, cur_idx, lineno):
auto_ifaces = re.split(self._ws_split_regex, lines[cur_idx])[1:]
if not auto_ifaces:
self._parse_error(self._currentfile, lineno,
'invalid auto line \'%s\''%lines[cur_idx])
return 0
for a in auto_ifaces:
if a == 'all':
self.auto_all = True
break
r = utils.parse_iface_range(a)
if r:
for i in range(r[1], r[2]):
self.auto_ifaces.append('%s-%d' %(r[0], i))
self.auto_ifaces.append(a)
return 0
def _add_to_iface_config(self, ifacename, iface_config, attrname,
attrval, lineno):
newattrname = attrname.replace("_", "-")
try:
if not self.callbacks.get('validateifaceattr')(newattrname,
attrval):
self._parse_error(self._currentfile, lineno,
'iface %s: unsupported keyword (%s)'
%(ifacename, attrname))
return
except:
pass
attrvallist = iface_config.get(newattrname, [])
if newattrname in ['scope', 'netmask', 'broadcast', 'preferred-lifetime']:
# For attributes that are related and that can have multiple
# entries, store them at the same index as their parent attribute.
# The example of such attributes is 'address' and its related
# attributes. since the related attributes can be optional,
# we add null string '' in places where they are optional.
# XXX: this introduces awareness of attribute names in
# this class which is a violation.
# get the index corresponding to the 'address'
addrlist = iface_config.get('address')
if addrlist:
# find the index of last address element
for i in range(0, len(addrlist) - len(attrvallist) -1):
attrvallist.append('')
attrvallist.append(attrval)
iface_config[newattrname] = attrvallist
elif not attrvallist:
iface_config[newattrname] = [attrval]
else:
iface_config[newattrname].append(attrval)
def parse_iface(self, lines, cur_idx, lineno, ifaceobj):
lines_consumed = 0
line_idx = cur_idx
iface_line = lines[cur_idx].strip(whitespaces)
iface_attrs = re.split(self._ws_split_regex, iface_line)
ifacename = iface_attrs[1]
# in cases where mako is unable to render the template
# or incorrectly renders it due to user template
# errors, we maybe left with interface names with
# mako variables in them. There is no easy way to
# recognize and warn about these. In the below check
# we try to warn the user of such cases by looking for
# variable patterns ('$') in interface names.
if '$' in ifacename:
self._parse_warn(self._currentfile, lineno,
'%s: unexpected characters in interface name' %ifacename)
ifaceobj.raw_config.append(iface_line)
iface_config = collections.OrderedDict()
for line_idx in range(cur_idx + 1, len(lines)):
l = lines[line_idx].strip(whitespaces)
if self.ignore_line(l) == 1:
continue
attrs = re.split(self._ws_split_regex, l, 1)
if self._is_keyword(attrs[0]):
line_idx -= 1
break
# if not a keyword, every line must have at least a key and value
if len(attrs) < 2:
self._parse_error(self._currentfile, line_idx,
'iface %s: invalid syntax \'%s\'' %(ifacename, l))
continue
ifaceobj.raw_config.append(l)
attrname = attrs[0]
# preprocess vars (XXX: only preprocesses $IFACE for now)
attrval = re.sub(r'\$IFACE', ifacename, attrs[1])
self._add_to_iface_config(ifacename, iface_config, attrname,
attrval, line_idx+1)
lines_consumed = line_idx - cur_idx
# Create iface object
if ifacename.find(':') != -1:
ifaceobj.name = ifacename.split(':')[0]
else:
ifaceobj.name = ifacename
ifaceobj.config = iface_config
ifaceobj.generate_env()
try:
ifaceobj.addr_family = iface_attrs[2]
ifaceobj.addr_method = iface_attrs[3]
except IndexError:
# ignore
pass
self._validate_addr_family(ifaceobj, lineno)
if self.auto_all or (ifaceobj.name in self.auto_ifaces):
ifaceobj.auto = True
classes = self.get_allow_classes_for_iface(ifaceobj.name)
if classes:
[ifaceobj.set_class(c) for c in classes]
return lines_consumed # Return next index
def process_iface(self, lines, cur_idx, lineno):
ifaceobj = iface()
lines_consumed = self.parse_iface(lines, cur_idx, lineno, ifaceobj)
range_val = utils.parse_iface_range(ifaceobj.name)
if range_val:
for v in range(range_val[1], range_val[2]):
ifaceobj_new = copy.deepcopy(ifaceobj)
ifaceobj_new.realname = '%s' %ifaceobj.name
ifaceobj_new.name = '%s%d' %(range_val[0], v)
ifaceobj_new.flags = iface.IFACERANGE_ENTRY
if v == range_val[1]:
ifaceobj_new.flags |= iface.IFACERANGE_START
self.callbacks.get('iface_found')(ifaceobj_new)
else:
self.callbacks.get('iface_found')(ifaceobj)
return lines_consumed # Return next index
def process_vlan(self, lines, cur_idx, lineno):
ifaceobj = iface()
lines_consumed = self.parse_iface(lines, cur_idx, lineno, ifaceobj)
range_val = utils.parse_iface_range(ifaceobj.name)
if range_val:
for v in range(range_val[1], range_val[2]):
ifaceobj_new = copy.deepcopy(ifaceobj)
ifaceobj_new.realname = '%s' %ifaceobj.name
ifaceobj_new.name = '%s%d' %(range_val[0], v)
ifaceobj_new.type = ifaceType.BRIDGE_VLAN
ifaceobj_new.flags = iface.IFACERANGE_ENTRY
if v == range_val[1]:
ifaceobj_new.flags |= iface.IFACERANGE_START
self.callbacks.get('iface_found')(ifaceobj_new)
else:
ifaceobj.type = ifaceType.BRIDGE_VLAN
self.callbacks.get('iface_found')(ifaceobj)
return lines_consumed # Return next index
network_elems = { 'source' : process_source,
'allow' : process_allow,
'auto' : process_auto,
'iface' : process_iface,
'vlan' : process_vlan}
def _is_keyword(self, str):
# The additional split here is for allow- keyword
tmp_str = str.split('-')[0]
if tmp_str in self.network_elems.keys():
return 1
return 0
def _get_keyword_func(self, str):
tmp_str = str.split('-')[0]
return self.network_elems.get(tmp_str)
def get_allow_classes_for_iface(self, ifacename):
classes = []
for class_name, ifacenames in self.allow_classes.items():
if ifacename in ifacenames:
classes.append(class_name)
return classes
def process_interfaces(self, filedata):
# process line continuations
filedata = ' '.join(d.strip() for d in filedata.split('\\'))
line_idx = 0
lines_consumed = 0
raw_config = filedata.split('\n')
lines = [l.strip(whitespaces) for l in raw_config]
while (line_idx < len(lines)):
if self.ignore_line(lines[line_idx]):
line_idx += 1
continue
words = re.split(self._ws_split_regex, lines[line_idx])
if not words:
line_idx += 1
continue
# Check if first element is a supported keyword
if self._is_keyword(words[0]):
keyword_func = self._get_keyword_func(words[0])
lines_consumed = keyword_func(self, lines, line_idx, line_idx+1)
line_idx += lines_consumed
else:
self._parse_error(self._currentfile, line_idx + 1,
'error processing line \'%s\'' %lines[line_idx])
line_idx += 1
return 0
def read_filedata(self, filedata):
self._currentfile_has_template = False
# run through template engine
try:
rendered_filedata = self._template_engine.render(filedata)
if rendered_filedata is filedata:
self._currentfile_has_template = False
else:
self._currentfile_has_template = True
except Exception as e:
self._parse_error(self._currentfile, -1,
'failed to render template (%s). ' %str(e) +
'Continue without template rendering ...')
rendered_filedata = None
pass
if rendered_filedata:
self.process_interfaces(rendered_filedata)
else:
self.process_interfaces(filedata)
def read_file(self, filename, fileiobuf=None):
if fileiobuf:
self.read_filedata(fileiobuf)
return
self._filestack.append(filename)
self.logger.info('processing interfaces file %s' %filename)
f = open(filename)
filedata = f.read()
f.close()
self.read_filedata(filedata)
self._filestack.pop()
def read_file_json(self, filename, fileiobuf=None):
if fileiobuf:
ifacedicts = json.loads(fileiobuf, encoding="utf-8")
#object_hook=ifaceJsonDecoder.json_object_hook)
elif filename:
self.logger.info('processing interfaces file %s' %filename)
fp = open(filename)
ifacedicts = json.load(fp)
#object_hook=ifaceJsonDecoder.json_object_hook)
# we need to handle both lists and non lists formats (e.g. {{}})
if not isinstance(ifacedicts,list):
ifacedicts = [ifacedicts]
for ifacedict in ifacedicts:
ifaceobj = ifaceJsonDecoder.json_to_ifaceobj(ifacedict)
if ifaceobj:
self._validate_addr_family(ifaceobj)
self.callbacks.get('validateifaceobj')(ifaceobj)
self.callbacks.get('iface_found')(ifaceobj)
def load(self):
""" This member function loads the networkinterfaces file.
Assumes networkinterfaces parser object is initialized with the
parser arguments
"""
if self.interfacesfileformat == 'json':
return self.read_file_json(self.interfacesfile,
self.interfacesfileiobuf)
return self.read_file(self.interfacesfile,
self.interfacesfileiobuf)

175
ifupdown/policymanager.py Normal file
View File

@@ -0,0 +1,175 @@
#!/usr/bin/python
#
# Copyright 2015 Cumulus Networks, Inc. All rights reserved.
#
#
'''
The PolicyManager should be subclassed by addon modules
to read a JSON policy config file that is later used to
set defaults:
Initialize: This module defines a list of config file location based
on module. There are defined in the __init__(): All the
addon modules need to do is import the policymanager module.
import ifupdown.policymanager as policymanager
Provides: an API to retrieve link attributes based on addon module name,
interface name, and attribute.
The ifupdown.policymanager module provides a global object policymanager_api
that can be called like so:
speed_default = policymanager.policymanager_api.get_default(
module_name='ethtool',
ifname=ifaceobj.name,
attr='link-speed'
)
'''
import json
import logging
import glob
class policymanager():
def __init__(self):
# we should check for these files in order
# so that customers can override the /var/lib file settings
self.logger = logging.getLogger('ifupdown.' +
self.__class__.__name__)
# we grab the json files from a known location and make sure that
# the defaults_policy is checked first
user_files = glob.glob('/etc/network/ifupdown2/policy.d/*.json')
# grab the default module files
default_files = glob.glob('/var/lib/ifupdownaddons/policy.d/*.json')
# keep an array of defaults indexed by module name
self.system_policy_array = {}
for filename in default_files:
system_array = {}
try:
fd = open(filename,'r')
system_array = json.load(fd)
self.logger.debug('reading %s system policy defaults config' \
% filename)
except Exception as e:
self.logger.debug('could not read %s system policy defaults config' \
% filename)
self.logger.debug(' exception is %s' % str(e))
for module in system_array.keys():
if self.system_policy_array.has_key(module):
self.logger.debug('warning: overwriting system module %s from file %s' \
% (module,filename))
self.system_policy_array[module] = system_array[module]
# take care of user defined policy defaults
self.user_policy_array = {}
for filename in user_files:
user_array = {}
try:
fd = open(filename,'r')
user_array = json.load(fd)
self.logger.debug('reading %s policy user defaults config' \
% filename)
except Exception as e:
self.logger.debug('could not read %s user policy defaults config' \
% filename)
self.logger.debug(' exception is %s' % str(e))
# customer added module attributes
for module in user_array.keys():
if self.system_policy_array.has_key(module):
# warn user that we are overriding the system module setting
self.logger.debug('warning: overwriting system with user module %s from file %s' \
% (module,filename))
self.user_policy_array[module] = user_array[module]
return
def get_iface_default(self,module_name=None,ifname=None,attr=None):
'''
get_iface_default: Addon modules must use one of two types of access methods to
the default configs. In this method, we expect the default to be
either in
[module]['iface_defaults'][ifname][attr] or
[module]['defaults'][attr]
We first check the user_policy_array and return that value. But if
the user did not specify an override, we use the system_policy_array.
'''
# make sure we have an index
if (not ifname or not attr or not module_name):
return None
val = None
# users can specify defaults to override the systemwide settings
# look for user specific interface attribute iface_defaults first
try:
# looks for user specified value
val = self.user_policy_array[module_name]['iface_defaults'][ifname][attr]
return val
except:
pass
try:
# failing that, there may be a user default for all intefaces
val = self.user_policy_array[module_name]['defaults'][attr]
return val
except:
pass
try:
# failing that, look for system setting for the interface
val = self.system_policy_array[module_name]['iface_defaults'][ifname][attr]
return val
except:
pass
try:
# failing that, look for system setting for all interfaces
val = self.system_policy_array[module_name]['defaults'][attr]
return val
except:
pass
# could not find any system or user default so return Non
return val
def get_attr_default(self,module_name=None,attr=None):
'''
get_attr_default: Addon modules must use one of two types of access methods to
the default configs. In this method, we expect the default to be in
[module][attr]
We first check the user_policy_array and return that value. But if
the user did not specify an override, we use the system_policy_array.
'''
if (not attr or not module_name):
return None
# users can specify defaults to override the systemwide settings
# look for user specific interface attribute iface_defaults first
val = None
if self.user_policy_array.get(module_name):
val = self.user_policy_array[module_name].get(attr)
if not val:
if self.system_policy_array.get(module_name):
val = self.system_policy_array[module_name].get(attr)
return val
def get_module_default(self,module_name=None):
'''
get_module_default: Addon modules can also access the entire config
This method returns indexed by "system" and "user": these are the
system-wide and user-defined policy arrays for a specific module.
'''
if not module_name:
return None
if self.system_policy_array.get(module_name) and \
self.user_policy_array.get(module_name):
mod_array = {"system":self.system_policy_array[module_name],
"user":self.user_policy_array[module_name]}
else:
# the module must not have these defined, return None
mod_array = None
return mod_array
policymanager_api = policymanager()

859
ifupdown/rtnetlink.py Normal file
View File

@@ -0,0 +1,859 @@
#!/usr/bin/env python
#
# Copyright 2014 Cumulus Networks, Inc. All rights reserved.
#
# Author: Scott Feldman, sfeldma@cumulusnetworks.com
# Author: Roopa Prabhu, roopa@cumulusnetworks.com
#
#
from socket import NETLINK_ROUTE, AF_INET, AF_INET6
from string import printable
from ipaddr import *
from ctypes import *
from netlink import *
import logging
logger = logging.getLogger(__name__)
#
# from /usr/include/linux/rtnetlink.h
#
RTMGRP_LINK = 0x1
RTMGRP_IPV4_IFADDR = 0x10
RTMGRP_IPV4_ROUTE = 0x40
RTMGRP_IPV6_IFADDR = 0x100
RTMGRP_IPV6_ROUTE = 0x400
RTM_NEWLINK = 16
RTM_DELLINK = 17
RTM_GETLINK = 18
RTM_SETLINK = 19
RTM_NEWADDR = 20
RTM_DELADDR = 21
RTM_GETADDR = 22
RTM_NEWROUTE = 24
RTM_DELROUTE = 25
RTM_GETROUTE = 26
# Definitions used in routing table administration.
class Nlmsg(Structure):
def _stringify(self):
return string_at(addressof(self), sizeof(self))
def __eq__(self, other):
return self._stringify() == other._stringify() and \
self.__dict__ == other.__dict__
def to_rta(self):
return Rtattr.from_address(addressof(self) + NLMSG_ALIGN(sizeof(self)))
def pack_extra(self, extra, addr):
memmove(addr, addressof(extra), sizeof(extra))
return NLMSG_ALIGN(sizeof(extra))
def pack_rtas(self, rtas, addr):
total_len = 0
for rta_type, value in rtas.iteritems():
rta = Rtattr.from_address(addr)
rta.rta_type = rta_type
pack_fn = self.rta_fn(rta_type)
rta_len = NLMSG_ALIGN(pack_fn(rta, value))
total_len += rta_len
addr += rta_len
return total_len
def pack_rtas_new(self, rtas, addr, policy):
total_len = 0
for rta_type, value in rtas.iteritems():
if type(value) == dict:
rta = Rtattr.from_address(addr)
rta.rta_type = rta_type
rta.rta_len = RTA_LENGTH(0)
rta_len = NLMSG_ALIGN(rta.rta_len)
total_len += rta_len
addr += rta_len
pack_fn = policy.get(rta_type)
rta_len = NLMSG_ALIGN(pack_fn(addr, value))
rta.rta_len += rta_len
else:
rta = Rtattr.from_address(addr)
rta.rta_type = rta_type
pack_fn = policy.get(rta_type)
rta_len = NLMSG_ALIGN(pack_fn(rta, value))
total_len += rta_len
addr += rta_len
return total_len
def rta_linkinfo(self, addr, rtas):
total_len = 0
# Check interface kind
kind = rtas.get(IFLA_INFO_KIND)
if kind == 'vlan':
data_policy = self.rta_linkinfo_data_vlan_policy()
else:
data_policy = self.rta_linkinfo_data_macvlan_policy()
# Pack info kind
rta = Rtattr.from_address(addr)
rta.rta_type = IFLA_INFO_KIND
rta_len = NLMSG_ALIGN(self.rta_string(rta, kind))
total_len += rta_len
addr += rta_len
# nest start link info data
rta = Rtattr.from_address(addr)
rta.rta_type = IFLA_INFO_DATA
rta.rta_len = RTA_LENGTH(0)
rta_len = NLMSG_ALIGN(rta.rta_len)
total_len += rta_len
addr += rta_len
rta_len = self.pack_rtas_new(rtas.get(IFLA_INFO_DATA), addr,
data_policy)
rta.rta_len += rta_len
total_len += rta_len
addr += rta_len
return total_len
def rta_bridge_vlan_info(self, rta, value):
if value:
data = RTA_DATA(rta)
memmove(data, addressof(value), sizeof(value))
rta.rta_len = RTA_LENGTH(sizeof(value))
return rta.rta_len
def rta_af_spec(self, addr, rtas):
total_len = 0
# XXX: Check family (Assumes bridge family for now)
rta_len = self.pack_rtas_new(rtas, addr,
self.rta_bridge_af_spec_policy())
total_len += rta_len
return total_len
def unpack_rtas(self, which_ones=[]):
len = self.nlh.nlmsg_len - NLMSG_LENGTH(sizeof(self))
rta = self.to_rta()
rtas = {}
while RTA_OK(rta, len):
rta_type = rta.rta_type
if not which_ones or rta_type in which_ones:
unpack_fn = self.rta_fn(rta_type)
rtas[rta_type] = unpack_fn(rta)
len, rta = RTA_NEXT(rta, len)
return rtas
def dump_rtas(self):
rtas = self.unpack_rtas()
for type, value in rtas.iteritems():
print ("rta", type, ":", value)
class _IPv6Addr(BigEndianStructure):
_fields_ = [
('upper', c_uint64),
('lower', c_uint64),
]
class _IPv4Addr(BigEndianStructure):
_fields_ = [
('addr', c_uint32),
]
def rta_uint8(self, rta, value=None):
data = RTA_DATA(rta)
if value:
c_uint8.from_address(data).value = value
rta.rta_len = RTA_LENGTH(sizeof(c_uint8))
return rta.rta_len
else:
return c_uint8.from_address(data).value
def rta_uint16(self, rta, value=None):
data = RTA_DATA(rta)
if value:
c_uint16.from_address(data).value = value
rta.rta_len = RTA_LENGTH(sizeof(c_uint16))
return rta.rta_len
else:
return c_uint16.from_address(data).value
def rta_uint32(self, rta, value=None):
data = RTA_DATA(rta)
if value:
c_uint32.from_address(data).value = value
rta.rta_len = RTA_LENGTH(sizeof(c_uint32))
return rta.rta_len
else:
return c_uint32.from_address(data).value
def rta_string(self, rta, value=None):
data = RTA_DATA(rta)
if value:
s = create_string_buffer(value)
memmove(data, addressof(s), len(value))
rta.rta_len = RTA_LENGTH(len(value))
return rta.rta_len
else:
return c_char_p(data).value
def rta_addr(self, rta, value=None):
data = RTA_DATA(rta)
if value:
if isinstance(value, IPv4Address):
self._IPv4Addr.from_address(data).addr = value._ip
rta.rta_len = RTA_LENGTH(sizeof(self._IPv4Addr))
elif isinstance(value, IPv6Address):
addr = self._IPv6Addr.from_address(data)
addr.upper = value._ip >> 64
addr.lower = value._ip & 0xffffffffffffffff
rta.rta_len = RTA_LENGTH(sizeof(self._IPv6Addr))
else:
assert(False)
return rta.rta_len
else:
if RTA_PAYLOAD(rta) == 4:
addr = c_uint32.__ctype_be__.from_address(data).value
addr = IPv4Address(addr)
else:
addr = self._IPv6Addr.from_address(data)
addr = IPv6Address((addr.upper << 64) + addr.lower)
return addr
def rta_uint8_array(self, rta, value=None):
data = RTA_DATA(rta)
if value:
s = (c_uint8 * len(value)).from_buffer_copy(value)
memmove(data, addressof(s), len(value))
rta.rta_len = RTA_LENGTH(len(value))
return rta.rta_len
else:
array = (c_uint8 * RTA_PAYLOAD(rta))()
memmove(array, data, RTA_PAYLOAD(rta))
return array
def rta_uint32_array(self, rta, value=None):
if value:
assert(False)
else:
data = RTA_DATA(rta)
size = RTA_PAYLOAD(rta) / sizeof(c_uint32)
array = (c_uint32 * size)()
memmove(array, data, RTA_PAYLOAD(rta))
return array
def rta_multipath(self, rta, value=None):
# XXX implement this
return None
def rta_wtf(self, rta, value=None):
return None
def rta_none(self, rta, value=None):
return None
def rta_fn(self, rta_type):
return None
# rtm_type
RTN_UNSPEC = 0
RTN_UNICAST = 1 # Gateway or direct route
RTN_LOCAL = 2 # Accept locally
RTN_BROADCAST = 3 # Accept locally as broadcast,
# send as broadcast
RTN_ANYCAST = 4 # Accept locally as broadcast,
# but send as unicast
RTN_MULTICAST = 5 # Multicast route
RTN_BLACKHOLE = 6 # Drop
RTN_UNREACHABLE = 7 # Destination is unreachable
RTN_PROHIBIT = 8 # Administratively prohibited
RTN_THROW = 9 # Not in this table
RTN_NAT = 10 # Translate this address
RTN_XRESOLVE = 11 # Use external resolver
RTN_MAX = 11
# rtm_protocol
RTPROT_UNSPEC = 0
RTPROT_REDIRECT = 1 # Route installed by ICMP redirects;
# not used by current IPv4
RTPROT_KERNEL = 2 # Route installed by kernel
RTPROT_BOOT = 3 # Route installed during boot
RTPROT_STATIC = 4 # Route installed by administrator
# Values of protocol >= RTPROT_STATIC are not interpreted by kernel;
# they are just passed from user and back as is.
# It will be used by hypothetical multiple routing daemons.
# Note that protocol values should be standardized in order to
# avoid conflicts.
RTPROT_GATED = 8 # Apparently, GateD
RTPROT_RA = 9 # RDISC/ND router advertisements
RTPROT_MRT = 10 # Merit MRT
RTPROT_ZEBRA = 11 # Zebra
RTPROT_BIRD = 12 # BIRD
RTPROT_DNROUTED = 13 # DECnet routing daemon
RTPROT_XORP = 14 # XORP
RTPROT_NTK = 15 # Netsukuku
RTPROT_DHCP = 16 # DHCP client
# rtm_scope
# Really it is not scope, but sort of distance to the destination.
# NOWHERE are reserved for not existing destinations, HOST is our
# local addresses, LINK are destinations, located on directly attached
# link and UNIVERSE is everywhere in the Universe.
# Intermediate values are also possible f.e. interior routes
# could be assigned a value between UNIVERSE and LINK.
RT_SCOPE_UNIVERSE = 0
# User defined values
RT_SCOPE_SITE = 200
RT_SCOPE_LINK = 253
RT_SCOPE_HOST = 254
RT_SCOPE_NOWHERE=255
# rtm_flags
RTM_F_NOTIFY = 0x100 # Notify user of route change
RTM_F_CLONED = 0x200 # This route is cloned
RTM_F_EQUALIZE = 0x400 # Multipath equalizer: NI
RTM_F_PREFIX = 0x800 # Prefix addresses
# Reserved table identifiers
RT_TABLE_UNSPEC = 0
# User defined values
RT_TABLE_COMPAT = 252
RT_TABLE_DEFAULT = 253
RT_TABLE_MAIN = 254
RT_TABLE_LOCAL = 255
RT_TABLE_MAX = 0xFFFFFFFF
# Generic structure for encapsulation of optional route information.
# It is reminiscent of sockaddr, but with sa_family replaced
# with attribute type.
class Rtattr(Structure):
_fields_ = [
('rta_len', c_uint16),
('rta_type', c_uint16),
]
# Routing message attributes
RTA_UNSPEC = 0
RTA_DST = 1
RTA_SRC = 2
RTA_IIF = 3
RTA_OIF = 4
RTA_GATEWAY = 5
RTA_PRIORITY = 6
RTA_PREFSRC = 7
RTA_METRICS = 8
RTA_MULTIPATH = 9
RTA_PROTOINFO = 10 # no longer used
RTA_FLOW = 11
RTA_CACHEINFO = 12
RTA_SESSION = 13 # no longer used
RTA_MP_ALGO = 14 # no longer used
RTA_TABLE = 15
RTA_MAX = 15
# Macros to handle rtattributes
RTA_ALIGNTO = 4
def RTA_ALIGN(len):
return (len + RTA_ALIGNTO - 1) & ~(RTA_ALIGNTO - 1)
def RTA_OK(rta, len):
return len >= sizeof(Rtattr) and \
rta.rta_len >= sizeof(Rtattr) and \
rta.rta_len <= len
def RTA_NEXT(rta, len):
cur = RTA_ALIGN(rta.rta_len)
rta = Rtattr.from_address(addressof(rta) + cur)
return len - cur, rta
def RTA_LENGTH(len):
return len + RTA_ALIGN(sizeof(Rtattr))
def RTA_SPACE(len):
return RTA_ALIGN(RTA_LENGTH(len))
def RTA_DATA(rta):
return addressof(rta) + RTA_LENGTH(0)
def RTA_PAYLOAD(rta):
return rta.rta_len - RTA_LENGTH(0)
RTNH_F_DEAD = 1 # Nexthop is dead (used by multipath)
RTNH_F_PERVASIVE = 2 # Do recursive gateway lookup
RTNH_F_ONLINK = 4 # Gateway is forced on link
# Reserved table identifiers
RT_TABLE_UNSPEC = 0
# User defined values
RT_TABLE_COMPAT = 252
RT_TABLE_DEFAULT = 253
RT_TABLE_MAIN = 254
RT_TABLE_LOCAL = 255
RT_TABLE_MAX = 0xFFFFFFFF
class Rtmsg(Nlmsg):
_fields_ = [
('rtm_family', c_uint8),
('rtm_dst_len', c_uint8),
('rtm_src_len', c_uint8),
('rtm_tos', c_uint8),
('rtm_table', c_uint8),
('rtm_protocol', c_uint8),
('rtm_scope', c_uint8),
('rtm_type', c_uint8),
('rtm_flags', c_uint32),
]
_table_str = {
RT_TABLE_UNSPEC: "unspecified",
RT_TABLE_COMPAT: "compat",
RT_TABLE_DEFAULT: "default",
RT_TABLE_MAIN: "main",
RT_TABLE_LOCAL: "local",
}
_proto_str = {
RTPROT_UNSPEC: "none",
RTPROT_REDIRECT: "redirect",
RTPROT_KERNEL: "kernel",
RTPROT_BOOT: "boot",
RTPROT_STATIC: "static",
RTPROT_GATED: "gated",
RTPROT_RA: "ra",
RTPROT_MRT: "mrtmrt",
RTPROT_ZEBRA: "zebra",
RTPROT_BIRD: "bird",
RTPROT_DNROUTED: "dnrouted",
RTPROT_XORP: "xorp",
RTPROT_NTK: "ntk",
RTPROT_DHCP: "dhcp",
}
_scope_str = {
RT_SCOPE_UNIVERSE: "universe",
RT_SCOPE_SITE: "site",
RT_SCOPE_LINK: "link",
RT_SCOPE_HOST: "host",
RT_SCOPE_NOWHERE: "nowhere",
}
_type_str = {
RTN_UNSPEC: "unspecified",
RTN_UNICAST: "unicast",
RTN_LOCAL: "local",
RTN_BROADCAST: "broadcast",
RTN_ANYCAST: "anycast",
RTN_MULTICAST: "multicast",
RTN_BLACKHOLE: "blackhole",
RTN_UNREACHABLE: "unreachable",
RTN_PROHIBIT: "prohibit",
RTN_THROW: "throw",
RTN_NAT: "nat",
RTN_XRESOLVE: "xresolve",
}
def dump(self):
print ('rtm_family', self.rtm_family)
print ('rtm_dst_len', self.rtm_dst_len)
print ('rtm_src_len', self.rtm_src_len)
print ('rtm_tos', self.rtm_tos)
print ('rtm_table', self._table_str.get(self.rtm_table, self.rtm_table))
print ('rtm_protocol', self._proto_str.get(self.rtm_protocol))
print ('rtm_scope', self._scope_str.get(self.rtm_scope))
print ('rtm_type', self._type_str.get(self.rtm_type))
print ('rtm_flags 0x%08x' % self.rtm_flags)
def rta_fn(self, rta_type):
fns = {
RTA_DST: self.rta_addr,
RTA_SRC: self.rta_addr,
RTA_IIF: self.rta_uint32,
RTA_OIF: self.rta_uint32,
RTA_GATEWAY: self.rta_addr,
RTA_PRIORITY: self.rta_uint32,
RTA_PREFSRC: self.rta_addr,
RTA_METRICS: self.rta_uint32_array,
RTA_MULTIPATH: self.rta_multipath,
RTA_PROTOINFO: self.rta_none,
RTA_FLOW: self.rta_uint32,
RTA_CACHEINFO: self.rta_none,
RTA_SESSION: self.rta_none,
RTA_MP_ALGO: self.rta_none,
RTA_TABLE: self.rta_uint32,
}
return fns.get(rta_type)
class Rtgenmsg(Nlmsg):
_fields_ = [
('rtgen_family', c_uint8),
]
def dump(self):
print ('rtgen_family', self.rtgen_family)
# New extended info filters for IFLA_EXT_MASK
RTEXT_FILTER_VF = (1 << 0)
# passes link level specific information, not dependent
# on network protocol.
IFLA_UNSPEC = 0
IFLA_ADDRESS = 1
IFLA_BROADCAST = 2
IFLA_IFNAME = 3
IFLA_MTU = 4
IFLA_LINK = 5
IFLA_QDISC = 6
IFLA_STATS = 7
IFLA_COST = 8
IFLA_PRIORITY = 9
IFLA_MASTER = 10
IFLA_WIRELESS = 11 # Wireless Extension event - see wireless.h
IFLA_PROTINFO = 12 # Protocol specific information for a link
IFLA_TXQLEN = 13
IFLA_MAP = 14
IFLA_WEIGHT = 15
IFLA_OPERSTATE = 16
IFLA_LINKMODE = 17
IFLA_LINKINFO = 18
IFLA_NET_NS_PID = 19
IFLA_IFALIAS = 20
IFLA_NUM_VF = 21 # Number of VFs if device is SR-IOV PF
IFLA_VFINFO_LIST = 22
IFLA_STATS64 = 23
IFLA_VF_PORTS = 24
IFLA_PORT_SELF = 25
IFLA_AF_SPEC = 26
IFLA_GROUP = 27 # Group the device belongs to
IFLA_NET_NS_FD = 28
IFLA_EXT_MASK = 29 # Extended info mask, VFs, etc
IFLA_MAX = 29
# IFLA_LINKINFO attributes
IFLA_INFO_UNSPEC = 0
IFLA_INFO_KIND = 1
IFLA_INFO_DATA = 2
IFLA_INFO_XSTATS = 3
IFLA_INFO_MAX = 4
# IFLA_LINKINFO_DATA attributes for vlan
IFLA_VLAN_UNSPEC = 0
IFLA_VLAN_ID = 1
# IFLA_LINKINFO_DATA attributes for macvlan
IFLA_MACVLAN_UNSPEC = 0
IFLA_MACVLAN_MODE = 1
# macvlan modes
MACVLAN_MODE_PRIVATE = 1
MACVLAN_MODE_VEPA = 2
MACVLAN_MODE_BRIDGE = 3
MACVLAN_MODE_PASSTHRU = 4
# BRIDGE IFLA_AF_SPEC attributes
IFLA_BRIDGE_FLAGS = 0
IFLA_BRIDGE_MODE = 1
IFLA_BRIDGE_VLAN_INFO = 2
# BRIDGE_VLAN_INFO flags
BRIDGE_VLAN_INFO_MASTER = 1
BRIDGE_VLAN_INFO_PVID = 2
BRIDGE_VLAN_INFO_UNTAGGED = 4
# Bridge flags
BRIDGE_FLAGS_MASTER = 1
BRIDGE_FLAGS_SELF = 2
class BridgeVlanInfo(Structure):
_fields_ = [
('flags', c_uint16),
('vid', c_uint16),
('vid_end', c_uint16),
]
class Ifinfomsg(Nlmsg):
_fields_ = [
('ifi_family', c_uint8),
('__ifi_pad', c_uint8),
('ifi_type', c_uint16), # ARPHRD_*
('ifi_index', c_int32), # Link index
('ifi_flags', c_uint32), # IFF_* flags
('ifi_change', c_uint32), # IFF_* change mask
]
def dump(self):
print ('ifi_family', self.ifi_family)
print ('ifi_type', self.ifi_type)
print ('ifi_index', self.ifi_index)
print ('ifi_flags 0x%08x' % self.ifi_flags)
print ('ifi_change 0x%08x' % self.ifi_change)
def rta_linkinfo_data_vlan_policy(self):
fns = {
IFLA_VLAN_ID : self.rta_uint16,
}
return fns
def rta_linkinfo_data_macvlan_policy(self):
fns = {
IFLA_MACVLAN_MODE : self.rta_uint32,
}
return fns
def rta_linkinfo_policy(self):
fns = {
IFLA_INFO_KIND : self.rta_string,
IFLA_INFO_DATA : self.rta_linkinfo_data,
}
return fns
def rta_bridge_af_spec_policy(self):
# Assume bridge family for now
fns = {
IFLA_BRIDGE_FLAGS : self.rta_uint16,
IFLA_BRIDGE_VLAN_INFO : self.rta_bridge_vlan_info,
}
return fns
def rta_policy(self):
fns = {
IFLA_UNSPEC: self.rta_wtf,
IFLA_ADDRESS: self.rta_uint8_array,
IFLA_BROADCAST: self.rta_uint8_array,
IFLA_IFNAME: self.rta_string,
IFLA_MTU: self.rta_uint32,
IFLA_LINK: self.rta_uint32,
IFLA_QDISC: self.rta_string,
IFLA_STATS: self.rta_none,
IFLA_COST: self.rta_none,
IFLA_PRIORITY: self.rta_none,
IFLA_MASTER: self.rta_uint32,
IFLA_WIRELESS: self.rta_none,
IFLA_PROTINFO: self.rta_none,
IFLA_TXQLEN: self.rta_uint32,
IFLA_MAP: self.rta_none,
IFLA_WEIGHT: self.rta_uint32,
IFLA_OPERSTATE: self.rta_uint8,
IFLA_LINKMODE: self.rta_uint8,
IFLA_LINKINFO: self.rta_linkinfo,
IFLA_NET_NS_PID: self.rta_uint32,
IFLA_IFALIAS: self.rta_string,
IFLA_NUM_VF: self.rta_uint32,
IFLA_VFINFO_LIST: self.rta_none,
IFLA_STATS64: self.rta_none,
IFLA_VF_PORTS: self.rta_none,
IFLA_PORT_SELF: self.rta_none,
IFLA_AF_SPEC: self.rta_af_spec,
IFLA_GROUP: self.rta_none,
IFLA_NET_NS_FD: self.rta_none,
IFLA_EXT_MASK: self.rta_none,
}
return fns;
def rta_fn(self, rta_type):
fns = {
IFLA_UNSPEC: self.rta_wtf,
IFLA_ADDRESS: self.rta_uint8_array,
IFLA_BROADCAST: self.rta_uint8_array,
IFLA_IFNAME: self.rta_string,
IFLA_MTU: self.rta_uint32,
IFLA_LINK: self.rta_uint32,
IFLA_QDISC: self.rta_string,
IFLA_STATS: self.rta_none,
IFLA_COST: self.rta_none,
IFLA_PRIORITY: self.rta_none,
IFLA_MASTER: self.rta_uint32,
IFLA_WIRELESS: self.rta_none,
IFLA_PROTINFO: self.rta_none,
IFLA_TXQLEN: self.rta_uint32,
IFLA_MAP: self.rta_none,
IFLA_WEIGHT: self.rta_uint32,
IFLA_OPERSTATE: self.rta_uint8,
IFLA_LINKMODE: self.rta_uint8,
IFLA_LINKINFO: self.rta_linkinfo,
IFLA_NET_NS_PID: self.rta_uint32,
IFLA_IFALIAS: self.rta_string,
IFLA_NUM_VF: self.rta_uint32,
IFLA_VFINFO_LIST: self.rta_none,
IFLA_STATS64: self.rta_none,
IFLA_VF_PORTS: self.rta_none,
IFLA_PORT_SELF: self.rta_none,
IFLA_AF_SPEC: self.rta_af_spec,
IFLA_GROUP: self.rta_none,
IFLA_NET_NS_FD: self.rta_none,
IFLA_EXT_MASK: self.rta_none,
}
return fns.get(rta_type)
# passes address specific information
# Important comment:
# IFA_ADDRESS is prefix address, rather than local interface address.
# It makes no difference for normally configured broadcast interfaces,
# but for point-to-point IFA_ADDRESS is DESTINATION address,
# local address is supplied in IFA_LOCAL attribute.
IFA_UNSPEC = 0
IFA_ADDRESS = 1
IFA_LOCAL = 2
IFA_LABEL = 3
IFA_BROADCAST = 4
IFA_ANYCAST = 5
IFA_CACHEINFO = 6
IFA_MULTICAST = 7
IFA_MAX = 7
class Ifaddrmsg(Nlmsg):
_fields_ = [
('ifa_family', c_uint8),
('ifa_prefixlen', c_uint8), # The prefix length
('ifa_flags', c_uint8), # Flags
('ifa_scope', c_uint8), # Address scope
('ifa_index', c_uint32), # Link index
]
_family_str = {
AF_INET: "inet",
AF_INET6: "inet6",
}
def dump(self):
print ('ifa_family', self.ifa_family)
print ('ifa_prefixlen', self.ifa_prefixlen)
print ('ifa_flags 0x%02x' % self.ifa_flags)
print ('ifa_scope', self.ifa_scope)
print ('ifa_index', self.ifa_index)
def rta_fn(self, rta_type):
fns = {
IFA_ADDRESS: self.rta_addr,
IFA_LOCAL: self.rta_addr,
IFA_LABEL: self.rta_string,
IFA_BROADCAST: self.rta_addr,
IFA_ANYCAST: self.rta_addr,
IFA_CACHEINFO: self.rta_none,
IFA_MULTICAST: self.rta_addr,
}
return fns.get(rta_type)
class RtNetlinkError(Exception):
def __init__(self, message):
Exception.__init__(self, message)
logger.error(message)
class RtNetlink(Netlink):
def __init__(self, pid):
Netlink.__init__(self, pid, NETLINK_ROUTE)
_rt_nlmsg_type_str = {
RTM_NEWROUTE: "RTM_NEWROUTE",
RTM_DELROUTE: "RTM_DELROUTE",
RTM_NEWLINK: "RTM_NEWLINK",
RTM_SETLINK: "RTM_SETLINK",
RTM_DELLINK: "RTM_DELLINK",
RTM_GETLINK: "RTM_GETLINK",
RTM_NEWADDR: "RTM_NEWADDR",
RTM_DELADDR: "RTM_DELADDR",
}
def _hexdump(self, buf):
while buf:
chunk = buf[:16]
buf = buf[16:]
nums = ["%02x" % c for c in chunk]
txt = [chr(c) if chr(c) in printable[:-5] else '.' for c in chunk]
print (" ".join(nums).ljust(48), "".join(txt))
def dump(self, nlh):
nlmsg = self.nlmsg(nlh)
print
self._hexdump(self.sendbuf[:nlh.nlmsg_len])
print
nlh.dump()
print
nlmsg.dump()
print
nlmsg.dump_rtas()
def nlmsg(self, nlh):
nlmsg_struct = {
RTM_NEWROUTE: Rtmsg,
RTM_DELROUTE: Rtmsg,
RTM_GETROUTE: Rtmsg,
RTM_NEWLINK: Ifinfomsg,
RTM_SETLINK: Ifinfomsg,
RTM_DELLINK: Ifinfomsg,
RTM_GETLINK: Rtgenmsg,
RTM_NEWADDR: Ifaddrmsg,
RTM_DELADDR: Ifaddrmsg,
RTM_GETADDR: Rtgenmsg,
}
nldata = NLMSG_DATA(nlh)
nlmsg = nlmsg_struct[nlh.nlmsg_type].from_address(nldata)
nlmsg.nlh = nlh
return nlmsg
def _nl_cb(self, nlh):
# print "nl cb", self._rt_nlmsg_type_str[nlh.nlmsg_type]
if nlh.nlmsg_type in self._cbs:
nlmsg = self.nlmsg(nlh)
# validate nl length
if nlh.nlmsg_len - NLMSG_LENGTH(sizeof(nlmsg)) < 0:
raise RtNetlinkError("invalid nl length")
self._cbs[nlh.nlmsg_type](nlh, nlmsg)
def bind(self, groups, cbs):
self._cbs = cbs
Netlink.bind(self, groups, self._nl_cb)
def request(self, nlmsg_type, flags, extra, rtas={}):
nlh = Nlmsghdr.from_buffer(self.sendbuf)
nlh_p = addressof(nlh)
seq = self.seq
pid = self.pid
nlh.nlmsg_len = NLMSG_HDRLEN()
nlh.nlmsg_type = nlmsg_type
nlh.nlmsg_flags = flags
nlh.nlmsg_pid = pid
nlh.nlmsg_seq = seq
nlmsg = self.nlmsg(nlh)
nlh.nlmsg_len += nlmsg.pack_extra(extra, nlh_p + nlh.nlmsg_len)
nlh.nlmsg_len += nlmsg.pack_rtas_new(rtas, nlh_p + nlh.nlmsg_len,
nlmsg.rta_policy())
#self.dump(nlh)
self.sendall(string_at(nlh_p, nlh.nlmsg_len))
self.seq += 1
token = (pid, seq)
return token

237
ifupdown/rtnetlink_api.py Normal file
View File

@@ -0,0 +1,237 @@
#!/usr/bin/env python
#
# Copyright 2014 Cumulus Networks, Inc. All rights reserved.
#
# Author: Roopa Prabhu, roopa@cumulusnetworks.com
#
#
from os import getpid
from socket import AF_UNSPEC
from socket import AF_BRIDGE
from iff import IFF_UP
from rtnetlink import *
import os
import ifupdownmain
class rtnetlinkApi(RtNetlink):
bind_done = False
def __init__(self, pid):
RtNetlink.__init__(self, pid)
self.logger = logging.getLogger('ifupdown.' +
self.__class__.__name__)
self.bind(0, None)
self.bind_done = True
self.ifindexmap = {}
def do_bind(self):
if self.bind_done:
return True
self.bind(0, None)
self.bind_done = True
def get_ifindex(self, ifname):
ifindex = self.ifindexmap.get(ifname)
if not ifindex:
with open('/sys/class/net/%s/ifindex' %ifname, 'r') as f:
ifindex = int(f.read())
self.ifindexmap[ifname] = ifindex
return ifindex
def create_vlan(self, link, ifname, vlanid):
self.logger.info('rtnetlink: creating vlan interface %s' %ifname)
if ifupdownmain.ifupdownFlags.DRYRUN:
return
try:
ifindex = self.get_ifindex(link)
except Exception as e:
raise Exception('cannot determine ifindex for link %s (%s)'
%(link, str(e)))
ifm = Ifinfomsg(AF_UNSPEC)
rtas = {IFLA_IFNAME: ifname,
IFLA_LINK : ifindex,
IFLA_LINKINFO : {
IFLA_INFO_KIND : 'vlan',
IFLA_INFO_DATA : {
IFLA_VLAN_ID : vlanid,
}
}
}
token = self.request(RTM_NEWLINK,
NLM_F_CREATE | NLM_F_REQUEST | NLM_F_ACK, ifm, rtas)
self.process_wait([token])
def create_macvlan(self, ifname, link, mode='private'):
self.logger.info('rtnetlink: creating macvlan interface %s' %ifname)
if ifupdownmain.ifupdownFlags.DRYRUN:
return
try:
ifindex = self.get_ifindex(link)
except Exception as e:
raise Exception('cannot determine ifindex for link %s (%s)'
%(link, str(e)))
ifm = Ifinfomsg(AF_UNSPEC)
rtas = {IFLA_IFNAME: ifname,
IFLA_LINK : ifindex,
IFLA_LINKINFO : {
IFLA_INFO_KIND : 'macvlan',
IFLA_INFO_DATA : {
IFLA_MACVLAN_MODE : MACVLAN_MODE_PRIVATE,
}
}
}
token = self.request(RTM_NEWLINK, NLM_F_CREATE | NLM_F_REQUEST |
NLM_F_ACK, ifm, rtas)
self.process_wait([token])
def link_set(self, ifname, state):
flags = 0
self.logger.info('rtnetlink: setting link %s %s' %(ifname, state))
if ifupdownmain.ifupdownFlags.DRYRUN:
return
if state == "up":
flags |= IFF_UP
else:
flags &= ~IFF_UP
ifm = Ifinfomsg(AF_UNSPEC, ifi_change=IFF_UP, ifi_flags=flags)
rtas = {IFLA_IFNAME: ifname}
token = self.request(RTM_NEWLINK, NLM_F_REQUEST | NLM_F_ACK, ifm, rtas)
self.process_wait([token])
def link_set_hwaddress(self, ifname, hwaddress):
flags = 0
self.logger.info('rtnetlink: setting link hwaddress %s %s' %(ifname, hwaddress))
if ifupdownmain.ifupdownFlags.DRYRUN:
return
flags &= ~IFF_UP
ifm = Ifinfomsg(AF_UNSPEC, ifi_change=IFF_UP)
rtas = {IFLA_IFNAME: ifname,
IFLA_ADDRESS : str(bytearray([int(a,16) for a in hwaddress.split(':')]))}
self.logger.info(rtas)
token = self.request(RTM_NEWLINK, NLM_F_REQUEST | NLM_F_ACK, ifm, rtas)
self.process_wait([token])
def addr_add(self, ifname, address, broadcast=None, peer=None, scope=None,
preferred_lifetime=None):
self.logger.info('rtnetlink: setting address')
if ifupdownmain.ifupdownFlags.DRYRUN:
return
try:
ifindex = self.get_ifindex(link)
except Exception as e:
raise Exception('cannot determine ifindex for link %s (%s)'
%(link, str(e)))
ifa_scope = RT_SCOPE_
if scope:
if scope == "universe":
ifa_scope = RT_SCOPE_UNIVERSE
elif scope == "site":
ifa_scope = RT_SCOPE_SITE
elif scope == "link":
ifa_scope = RT_SCOPE_LINK
elif scope == "host":
ifa_scope = RT_SCOPE_HOST
elif scope == "nowhere":
ifa_scope = RT_SCOPE_NOWHERE
rtas = {IFLA_ADDRESS: ifname}
ifa = Ifaddrmsg(AF_UNSPEC, ifa_scope=ifa_scope, ifa_index=ifindex)
token = self.request(RTM_NEWADDR, NLM_F_REQUEST | NLM_F_ACK, ifa, rtas)
self.process_wait([token])
def link_set_many(self, ifname, ifattrs):
_ifattr_to_rta_map = {'dev' : IFLA_NAME,
'address' : IFLA_ADDRESS,
'broadcast' : IFLA_BROADCAST,
'mtu' : IFLA_MTU,
'master' : IFLA_MASTER}
flags = 0
ifi_change = IFF_UP
rtas = {}
self.logger.info('rtnetlink: setting link %s %s' %(ifname, state))
if ifupdownmain.ifupdownFlags.DRYRUN:
return
if not ifattrs:
return
state = ifattrs.get('state')
if state == 'up':
flags |= IFF_UP
elif state == 'down':
flags &= ~IFF_UP
else:
ifi_change = 0
if ifi_change:
ifm = Ifinfomsg(AF_UNSPEC, ifi_change=IFF_UP, ifi_flags=flags)
else:
ifm = Ifinfomsg(AF_UNSPEC)
for attr, attrval in ifattrs.items():
rta_attr = _ifattr_to_rta_map.get(attr)
if rta_attr:
if attr == 'hwaddress':
rtas[rta_attr] = str(bytearray([int(a,16) for a in attrval.split(':')]))
else:
rtas[rta_attr] = attrval
token = self.request(RTM_NEWLINK, NLM_F_REQUEST | NLM_F_ACK, ifm, rtas)
self.process_wait([token])
def bridge_vlan(self, add=True, vid=None, dev=None, pvid=False,
untagged=False, master=True):
flags = 0
vflags = 0
if not vid or not dev:
return
self.logger.info('rtnetlink: bridge vlan add vid %s %s %s dev %s %s'
%(vid, 'untagged' if untagged else '',
'pvid' if pvid else '', dev,
'self' if self else ''))
if ifupdownmain.ifupdownFlags.DRYRUN:
return
try:
ifindex = self.get_ifindex(dev)
except Exception as e:
raise Exception('cannot determine ifindex for dev %s (%s)'
%(dev, str(e)))
if not master:
flags = BRIDGE_FLAGS_SELF
if pvid:
vflags = BRIDGE_VLAN_INFO_PVID
vflags |= BRIDGE_VLAN_INFO_UNTAGGED
elif untagged:
vflags |= BRIDGE_VLAN_INFO_UNTAGGED
ifm = Ifinfomsg(AF_BRIDGE, ifi_index=ifindex)
rtas = {IFLA_AF_SPEC: {
IFLA_BRIDGE_FLAGS: flags,
IFLA_BRIDGE_VLAN_INFO : BridgeVlanInfo(vflags, int(vid), int(vid))
}
}
if add:
token = self.request(RTM_SETLINK,
NLM_F_REQUEST | NLM_F_ACK, ifm, rtas)
else:
token = self.request(RTM_DELLINK,
NLM_F_REQUEST | NLM_F_ACK, ifm, rtas)
self.process_wait([token])
def bridge_vlan_many(self, add=True, vids=[], dev=None, pvid=False,
untagged=False, master=True):
for v in vids:
self.bridge_vlan_add(add, v, dev, ispvid, isuntagged, master)
rtnl_api = rtnetlinkApi(os.getpid())

531
ifupdown/scheduler.py Normal file
View File

@@ -0,0 +1,531 @@
#!/usr/bin/python
#
# Copyright 2014 Cumulus Networks, Inc. All rights reserved.
# Author: Roopa Prabhu, roopa@cumulusnetworks.com
#
# ifaceScheduler --
# interface scheduler
#
from statemanager import *
from iface import *
from graph import *
from collections import deque
from collections import OrderedDict
import logging
import traceback
import sys
from graph import *
from collections import deque
from threading import *
from ifupdownbase import *
from sets import Set
class ifaceSchedulerFlags():
""" Enumerates scheduler flags """
INORDER = 0x1
POSTORDER = 0x2
class ifaceScheduler():
""" scheduler functions to schedule configuration of interfaces.
supports scheduling of interfaces serially in plain interface list
or dependency graph format.
"""
_STATE_CHECK = True
_SCHED_RETVAL = True
@classmethod
def run_iface_op(cls, ifupdownobj, ifaceobj, op, cenv=None):
""" Runs sub operation on an interface """
ifacename = ifaceobj.name
if ifupdownobj.type and ifupdownobj.type != ifaceobj.type:
return
if not ifupdownobj.ADDONS_ENABLE: return
if op == 'query-checkcurr':
query_ifaceobj=ifupdownobj.create_n_save_ifaceobjcurr(ifaceobj)
# If not type bridge vlan and the object does not exist,
# mark not found and return
if (not ifupdownobj.link_exists(ifaceobj.name) and
ifaceobj.type != ifaceType.BRIDGE_VLAN):
query_ifaceobj.set_state_n_status(ifaceState.from_str(op),
ifaceStatus.NOTFOUND)
return
for mname in ifupdownobj.module_ops.get(op):
m = ifupdownobj.modules.get(mname)
err = 0
try:
if hasattr(m, 'run'):
msg = ('%s: %s : running module %s' %(ifacename, op, mname))
if op == 'query-checkcurr':
# Dont check curr if the interface object was
# auto generated
if (ifaceobj.priv_flags & ifupdownobj.NOCONFIG):
continue
ifupdownobj.logger.debug(msg)
m.run(ifaceobj, op, query_ifaceobj,
ifaceobj_getfunc=ifupdownobj.get_ifaceobjs)
else:
ifupdownobj.logger.debug(msg)
m.run(ifaceobj, op,
ifaceobj_getfunc=ifupdownobj.get_ifaceobjs)
except Exception as e:
if not ifupdownobj.ignore_error(str(e)):
err = 1
ifupdownobj.logger.warn(str(e))
# Continue with rest of the modules
pass
finally:
if err or ifaceobj.status == ifaceStatus.ERROR:
ifaceobj.set_state_n_status(ifaceState.from_str(op),
ifaceStatus.ERROR)
if 'up' in op or 'down' in op:
cls._SCHED_RETVAL = False
else:
# Mark success only if the interface was not already
# marked with error
status = (ifaceobj.status
if ifaceobj.status == ifaceStatus.ERROR
else ifaceStatus.SUCCESS)
ifaceobj.set_state_n_status(ifaceState.from_str(op),
status)
if ifupdownobj.config.get('addon_scripts_support', '0') == '1':
# execute /etc/network/ scripts
for mname in ifupdownobj.script_ops.get(op, []):
ifupdownobj.logger.debug('%s: %s : running script %s'
%(ifacename, op, mname))
try:
ifupdownobj.exec_command(mname, cmdenv=cenv)
except Exception as e:
ifupdownobj.log_error(str(e))
@classmethod
def run_iface_list_ops(cls, ifupdownobj, ifaceobjs, ops):
""" Runs all operations on a list of interface
configurations for the same interface
"""
# minor optimization. If operation is 'down', proceed only
# if interface exists in the system
ifacename = ifaceobjs[0].name
ifupdownobj.logger.info('%s: running ops ...' %ifacename)
if ('down' in ops[0] and
ifaceobjs[0].type != ifaceType.BRIDGE_VLAN and
not ifupdownobj.link_exists(ifacename)):
ifupdownobj.logger.debug('%s: does not exist' %ifacename)
# run posthook before you get out of here, so that
# appropriate cleanup is done
posthookfunc = ifupdownobj.sched_hooks.get('posthook')
if posthookfunc:
for ifaceobj in ifaceobjs:
ifaceobj.status = ifaceStatus.SUCCESS
posthookfunc(ifupdownobj, ifaceobj, 'down')
return
for op in ops:
# first run ifupdownobj handlers. This is good enough
# for the first object in the list
handler = ifupdownobj.ops_handlers.get(op)
if handler:
try:
handler(ifupdownobj, ifaceobjs[0])
except Exception as e:
if not ifupdownobj.link_master_slave_ignore_error(str(e)):
ifupdownobj.logger.warn('%s: %s'
%(ifaceobjs[0].name, str(e)))
pass
for ifaceobj in ifaceobjs:
cls.run_iface_op(ifupdownobj, ifaceobj, op,
cenv=ifupdownobj.generate_running_env(ifaceobj, op)
if ifupdownobj.config.get('addon_scripts_support',
'0') == '1' else None)
posthookfunc = ifupdownobj.sched_hooks.get('posthook')
if posthookfunc:
try:
[posthookfunc(ifupdownobj, ifaceobj, ops[0])
for ifaceobj in ifaceobjs]
except Exception as e:
ifupdownobj.logger.warn('%s' %str(e))
pass
@classmethod
def _check_upperifaces(cls, ifupdownobj, ifaceobj, ops, parent,
followdependents=False):
""" Check if upperifaces are hanging off us and help caller decide
if he can proceed with the ops on this device
Returns True or False indicating the caller to proceed with the
operation.
"""
# proceed only for down operation
if 'down' not in ops[0]:
return True
if (ifupdownobj.FORCE or
not ifupdownobj.ADDONS_ENABLE or
(not ifupdownobj.is_ifaceobj_noconfig(ifaceobj) and
ifupdownobj.config.get('warn_on_ifdown', '0') == '0' and
not ifupdownobj.ALL)):
return True
ulist = ifaceobj.upperifaces
if not ulist:
return True
# Get the list of upper ifaces other than the parent
tmpulist = ([u for u in ulist if u != parent] if parent
else ulist)
if not tmpulist:
return True
# XXX: This is expensive. Find a cheaper way to do this.
# if any of the upperdevs are present,
# return false to the caller to skip this interface
for u in tmpulist:
if ifupdownobj.link_exists(u):
if not ifupdownobj.ALL:
if ifupdownobj.is_ifaceobj_noconfig(ifaceobj):
ifupdownobj.logger.info('%s: skipping interface down,'
%ifaceobj.name + ' upperiface %s still around ' %u)
else:
ifupdownobj.logger.warn('%s: skipping interface down,'
%ifaceobj.name + ' upperiface %s still around ' %u)
return False
return True
@classmethod
def run_iface_graph(cls, ifupdownobj, ifacename, ops, parent=None,
order=ifaceSchedulerFlags.POSTORDER,
followdependents=True):
""" runs interface by traversing all nodes rooted at itself """
# Each ifacename can have a list of iface objects
ifaceobjs = ifupdownobj.get_ifaceobjs(ifacename)
if not ifaceobjs:
raise Exception('%s: not found' %ifacename)
# Check state of the dependent. If it is already brought up, return
if (cls._STATE_CHECK and
(ifaceobjs[0].state == ifaceState.from_str(ops[-1]))):
ifupdownobj.logger.debug('%s: already processed' %ifacename)
return
for ifaceobj in ifaceobjs:
if not cls._check_upperifaces(ifupdownobj, ifaceobj,
ops, parent, followdependents):
return
# If inorder, run the iface first and then its dependents
if order == ifaceSchedulerFlags.INORDER:
cls.run_iface_list_ops(ifupdownobj, ifaceobjs, ops)
for ifaceobj in ifaceobjs:
# Run lowerifaces or dependents
dlist = ifaceobj.lowerifaces
if dlist:
ifupdownobj.logger.debug('%s: found dependents %s'
%(ifacename, str(dlist)))
try:
if not followdependents:
# XXX: this is yet another extra step,
# but is needed for interfaces that are
# implicit dependents. even though we are asked to
# not follow dependents, we must follow the ones
# that dont have user given config. Because we own them
new_dlist = [d for d in dlist
if ifupdownobj.is_iface_noconfig(d)]
if new_dlist:
cls.run_iface_list(ifupdownobj, new_dlist, ops,
ifacename, order, followdependents,
continueonfailure=False)
else:
cls.run_iface_list(ifupdownobj, dlist, ops,
ifacename, order,
followdependents,
continueonfailure=False)
except Exception as e:
if (ifupdownobj.ignore_error(str(e))):
pass
else:
# Dont bring the iface up if children did not come up
ifaceobj.set_state_n_status(ifaceState.NEW,
ifaceStatus.ERROR)
raise
if order == ifaceSchedulerFlags.POSTORDER:
cls.run_iface_list_ops(ifupdownobj, ifaceobjs, ops)
@classmethod
def run_iface_list(cls, ifupdownobj, ifacenames,
ops, parent=None, order=ifaceSchedulerFlags.POSTORDER,
followdependents=True, continueonfailure=True):
""" Runs interface list """
for ifacename in ifacenames:
try:
cls.run_iface_graph(ifupdownobj, ifacename, ops, parent,
order, followdependents)
except Exception as e:
if continueonfailure:
if ifupdownobj.logger.isEnabledFor(logging.DEBUG):
traceback.print_tb(sys.exc_info()[2])
ifupdownobj.logger.error('%s : %s' %(ifacename, str(e)))
pass
else:
if (ifupdownobj.ignore_error(str(e))):
pass
else:
raise Exception('%s : (%s)' %(ifacename, str(e)))
@classmethod
def run_iface_graph_upper(cls, ifupdownobj, ifacename, ops, parent=None,
followdependents=True, skip_root=False):
""" runs interface by traversing all nodes rooted at itself """
# Each ifacename can have a list of iface objects
ifaceobjs = ifupdownobj.get_ifaceobjs(ifacename)
if not ifaceobjs:
raise Exception('%s: not found' %ifacename)
if (cls._STATE_CHECK and
(ifaceobjs[0].state == ifaceState.from_str(ops[-1]))):
ifupdownobj.logger.debug('%s: already processed' %ifacename)
return
if not skip_root:
# run the iface first and then its upperifaces
cls.run_iface_list_ops(ifupdownobj, ifaceobjs, ops)
for ifaceobj in ifaceobjs:
# Run upperifaces
ulist = ifaceobj.upperifaces
if ulist:
ifupdownobj.logger.debug('%s: found upperifaces %s'
%(ifacename, str(ulist)))
try:
cls.run_iface_list_upper(ifupdownobj, ulist, ops,
ifacename,
followdependents,
continueonfailure=True)
except Exception as e:
if (ifupdownobj.ignore_error(str(e))):
pass
else:
raise
@classmethod
def run_iface_list_upper(cls, ifupdownobj, ifacenames,
ops, parent=None, followdependents=True,
continueonfailure=True, skip_root=False):
""" Runs interface list """
for ifacename in ifacenames:
try:
cls.run_iface_graph_upper(ifupdownobj, ifacename, ops, parent,
followdependents, skip_root)
except Exception as e:
if ifupdownobj.logger.isEnabledFor(logging.DEBUG):
traceback.print_tb(sys.exc_info()[2])
ifupdownobj.logger.warn('%s : %s' %(ifacename, str(e)))
pass
@classmethod
def _get_valid_upperifaces(cls, ifupdownobj, ifacenames,
allupperifacenames):
""" Recursively find valid upperifaces
valid upperifaces are:
- An upperiface which had no user config (example builtin
interfaces. usually vlan interfaces.)
- or had config and previously up
- and interface currently does not exist
- or is a bridge (because if your upperiface was a bridge
- u will have to execute up on the bridge
to enslave the port and apply bridge attributes to the port) """
upperifacenames = []
for ifacename in ifacenames:
# get upperifaces
ifaceobj = ifupdownobj.get_ifaceobj_first(ifacename)
if not ifaceobj:
continue
ulist = Set(ifaceobj.upperifaces).difference(upperifacenames)
nulist = []
for u in ulist:
uifaceobj = ifupdownobj.get_ifaceobj_first(u)
if not uifaceobj:
continue
has_config = not bool(uifaceobj.priv_flags
& ifupdownobj.NOCONFIG)
if (((has_config and ifupdownobj.get_ifaceobjs_saved(u)) or
not has_config) and (not ifupdownobj.link_exists(u)
or uifaceobj.link_kind == ifaceLinkKind.BRIDGE)):
nulist.append(u)
upperifacenames.extend(nulist)
allupperifacenames.extend(upperifacenames)
if upperifacenames:
cls._get_valid_upperifaces(ifupdownobj, upperifacenames,
allupperifacenames)
return
@classmethod
def run_upperifaces(cls, ifupdownobj, ifacenames, ops,
continueonfailure=True):
""" Run through valid upperifaces """
upperifaces = []
cls._get_valid_upperifaces(ifupdownobj, ifacenames, upperifaces)
if not upperifaces:
return
# dump valid upperifaces
ifupdownobj.logger.debug(upperifaces)
for u in upperifaces:
try:
ifaceobjs = ifupdownobj.get_ifaceobjs(u)
if not ifaceobjs:
continue
cls.run_iface_list_ops(ifupdownobj, ifaceobjs, ops)
except Exception as e:
if continueonfailure:
self.logger.warn('%s' %str(e))
@classmethod
def get_sorted_iface_list(cls, ifupdownobj, ifacenames, ops,
dependency_graph, indegrees=None):
if len(ifacenames) == 1:
return ifacenames
# Get a sorted list of all interfaces
if not indegrees:
indegrees = OrderedDict()
for ifacename in dependency_graph.keys():
indegrees[ifacename] = ifupdownobj.get_iface_refcnt(ifacename)
ifacenames_all_sorted = graph.topological_sort_graphs_all(
dependency_graph, indegrees)
# if ALL was set, return all interfaces
if ifupdownobj.ALL:
return ifacenames_all_sorted
# else return ifacenames passed as argument in sorted order
ifacenames_sorted = []
[ifacenames_sorted.append(ifacename)
for ifacename in ifacenames_all_sorted
if ifacename in ifacenames]
return ifacenames_sorted
@classmethod
def sched_ifaces(cls, ifupdownobj, ifacenames, ops,
dependency_graph=None, indegrees=None,
order=ifaceSchedulerFlags.POSTORDER,
followdependents=True, skipupperifaces=False, sort=False):
""" runs interface configuration modules on interfaces passed as
argument. Runs topological sort on interface dependency graph.
Args:
**ifupdownobj** (object): ifupdownMain object
**ifacenames** (list): list of interface names
**ops** : list of operations to perform eg ['pre-up', 'up', 'post-up']
**dependency_graph** (dict): dependency graph in adjacency list format
Kwargs:
**indegrees** (dict): indegree array of the dependency graph
**order** (int): ifaceSchedulerFlags (POSTORDER, INORDER)
**followdependents** (bool): follow dependent interfaces if true
**sort** (bool): sort ifacelist in the case where ALL is not set
"""
#
# Algo:
# if ALL/auto interfaces are specified,
# - walk the dependency tree in postorder or inorder depending
# on the operation.
# (This is to run interfaces correctly in order)
# else:
# - sort iface list if the ifaces belong to a "class"
# - else just run iface list in the order they were specified
#
# Run any upperifaces if available
#
followupperifaces = False
run_queue = []
skip_ifacesort = int(ifupdownobj.config.get('skip_ifacesort', '0'))
if not skip_ifacesort and not indegrees:
indegrees = OrderedDict()
for ifacename in dependency_graph.keys():
indegrees[ifacename] = ifupdownobj.get_iface_refcnt(ifacename)
if not ifupdownobj.ALL:
if 'up' in ops[0]:
# If there is any interface that does not exist, maybe it
# is a logical interface and we have to followupperifaces
# when it comes up, so lets get that list.
if any([True for i in ifacenames
if ifupdownobj.must_follow_upperifaces(i)]):
followupperifaces = (True if
[i for i in ifacenames
if not ifupdownobj.link_exists(i)]
else False)
# sort interfaces only if the caller asked to sort
# and skip_ifacesort is not on.
if not skip_ifacesort and sort:
run_queue = cls.get_sorted_iface_list(ifupdownobj, ifacenames,
ops, dependency_graph, indegrees)
if run_queue and 'up' in ops[0]:
run_queue.reverse()
else:
# if -a is set, we pick the interfaces
# that have no parents and use a sorted list of those
if not skip_ifacesort:
sorted_ifacenames = cls.get_sorted_iface_list(ifupdownobj,
ifacenames, ops, dependency_graph,
indegrees)
if sorted_ifacenames:
# pick interfaces that user asked
# and those that dont have any dependents first
[run_queue.append(ifacename)
for ifacename in sorted_ifacenames
if ifacename in ifacenames and
not indegrees.get(ifacename)]
ifupdownobj.logger.debug('graph roots (interfaces that ' +
'dont have dependents):' + ' %s' %str(run_queue))
else:
ifupdownobj.logger.warn('interface sort returned None')
# If queue not present, just run interfaces that were asked by the
# user
if not run_queue:
run_queue = list(ifacenames)
# if we are taking the order of interfaces as specified
# in the interfaces file, we should reverse the list if we
# want to down. This can happen if 'skip_ifacesort'
# is been specified.
if 'down' in ops[0]:
run_queue.reverse()
# run interface list
cls.run_iface_list(ifupdownobj, run_queue, ops,
parent=None, order=order,
followdependents=followdependents)
if not cls._SCHED_RETVAL:
raise Exception()
if (not skipupperifaces and
ifupdownobj.config.get('skip_upperifaces', '0') == '0' and
((not ifupdownobj.ALL and followdependents) or
followupperifaces) and
'up' in ops[0]):
# If user had given a set of interfaces to bring up
# try and execute 'up' on the upperifaces
ifupdownobj.logger.info('running upperifaces (parent interfaces) ' +
'if available ..')
cls._STATE_CHECK = False
cls.run_upperifaces(ifupdownobj, ifacenames, ops)
cls._STATE_CHECK = True

176
ifupdown/statemanager.py Normal file
View File

@@ -0,0 +1,176 @@
#!/usr/bin/python
#
# Copyright 2014 Cumulus Networks, Inc. All rights reserved.
# Author: Roopa Prabhu, roopa@cumulusnetworks.com
#
# stateManager --
# interface state manager
#
import cPickle
from collections import OrderedDict
import logging
import os
from iface import *
class pickling():
""" class with helper methods for pickling/unpickling iface objects """
@classmethod
def save(cls, filename, list_of_objects):
""" pickle a list of iface objects """
try:
with open(filename, 'w') as f:
for obj in list_of_objects:
cPickle.dump(obj, f, cPickle.HIGHEST_PROTOCOL)
except:
raise
@classmethod
def save_obj(cls, f, obj):
""" pickle iface object """
try:
cPickle.dump(obj, f, cPickle.HIGHEST_PROTOCOL)
except:
raise
@classmethod
def load(cls, filename):
""" load picked iface object """
with open(filename, 'r') as f:
while True:
try: yield cPickle.load(f)
except EOFError: break
except: raise
class stateManager():
""" state manager for managing ifupdown iface obj state
ifupdown2 has to maitain old objects for down operation on
interfaces. ie to down or delete old configuration.
This class uses pickle to store iface objects.
"""
state_dir = '/run/network/'
"""directory where the state file is stored """
state_filename = 'ifstatenew'
"""name of the satefile """
def __init__(self):
""" Initializes statemanager internal state
which includes a dictionary of last pickled iface objects
"""
self.ifaceobjdict = OrderedDict()
self.logger = logging.getLogger('ifupdown.' +
self.__class__.__name__)
if not os.path.exists(self.state_dir):
os.mkdir(self.state_dir)
self.state_file = self.state_dir + self.state_filename
def save_ifaceobj(self, ifaceobj):
self.ifaceobjdict.setdefault(ifaceobj.name,
[]).append(ifaceobj)
def read_saved_state(self, filename=None):
"""This member function reads saved iface objects
Kwargs:
filename (str): name of the state file
"""
pickle_filename = filename
if not pickle_filename:
pickle_filename = self.state_file
if not os.path.exists(pickle_filename):
return
for ifaceobj in pickling.load(pickle_filename):
self.save_ifaceobj(ifaceobj)
def get_ifaceobjs(self, ifacename):
return self.ifaceobjdict.get(ifacename)
def ifaceobj_sync(self, ifaceobj, op):
"""This member function sync's new obj state to old statemanager state
Args:
ifaceobj (object): new iface object
op (str): ifupdown operation
"""
self.logger.debug('%s: statemanager sync state %s'
%(ifaceobj.name, op))
old_ifaceobjs = self.ifaceobjdict.get(ifaceobj.name)
if 'up' in op:
if not old_ifaceobjs:
self.ifaceobjdict[ifaceobj.name] = [ifaceobj]
else:
# If it matches any of the object, return
if any(o.compare(ifaceobj) for o in old_ifaceobjs):
return
# If it does not match any of the objects, and if
# all objs in the list came from the pickled file,
# then reset the list and add this object as a fresh one,
# else append to the list
if old_ifaceobjs[0].flags & iface._PICKLED:
del self.ifaceobjdict[ifaceobj.name]
self.ifaceobjdict[ifaceobj.name] = [ifaceobj]
else:
self.ifaceobjdict[ifaceobj.name].append(ifaceobj)
elif 'down' in op:
# If down of object successfull, delete object from state manager
if not old_ifaceobjs:
return
if ifaceobj.status != ifaceStatus.SUCCESS:
return
# If it matches any of the object, return
oidx = 0
for o in old_ifaceobjs:
if o.compare(ifaceobj):
old_ifaceobjs.pop(oidx)
if not len(old_ifaceobjs):
del self.ifaceobjdict[ifaceobj.name]
return
oidx += 1
def save_state(self):
""" saves state (ifaceobjects) to persistent state file """
try:
with open(self.state_file, 'w') as f:
if not len(self.ifaceobjdict):
f.truncate(0)
return
self.logger.debug('saving state ..')
for ifaceobjs in self.ifaceobjdict.values():
[pickling.save_obj(f, i) for i in ifaceobjs]
except:
raise
def dump_pretty(self, ifacenames, format='native'):
if not ifacenames:
ifacenames = self.ifaceobjdict.keys()
for i in ifacenames:
ifaceobjs = self.get_ifaceobjs(i)
if not ifaceobjs:
continue
for ifaceobj in ifaceobjs:
if format == 'json':
ifaceobj.dump_json()
else:
ifaceobj.dump_pretty()
def dump(self, ifacenames=None):
self.logger.debug('statemanager iface state:')
if ifacenames:
for i in ifacenames:
ifaceobj = self.ifaces.get(i)
if ifaceobj is None:
raise ifaceNotFoundError('ifname %s'
%i + ' not found')
ifaceobj.dump(self.logger)
else:
for ifacename, ifaceobjs in self.ifaceobjdict.items():
[i.dump(self.logger) for i in ifaceobjs]

59
ifupdown/template.py Normal file
View File

@@ -0,0 +1,59 @@
#!/usr/bin/python
#
# Copyright 2014 Cumulus Networks, Inc. All rights reserved.
# Author: Roopa Prabhu, roopa@cumulusnetworks.com
#
# template --
# helper class to render templates
#
import logging
import traceback
from utils import *
class templateEngine():
""" provides template rendering methods """
def __init__(self, template_engine, template_lookuppath=None):
self.logger = logging.getLogger('ifupdown.' +
self.__class__.__name__)
self.tclass = None
self.tclassargs = {}
self.render = self._render_default
if template_engine == 'mako':
try:
self.tclass = utils.importName('mako.template', 'Template')
except Exception as e:
self.logger.warn('unable to load template engine %s (%s)'
%(template_engine, str(e)))
pass
if template_lookuppath:
try:
self.logger.debug('setting template lookuppath to %s'
%template_lookuppath)
lc = utils.importName('mako.lookup', 'TemplateLookup')
self.tclassargs['lookup'] = lc(
directories=template_lookuppath.split(':'))
except Exception as e:
self.logger.warn('unable to set template lookup path' +
' %s (%s)' %(template_lookuppath, str(e)))
pass
self.render = self._render_mako
else:
self.logger.info('skip template processing.., ' +
'template engine not found')
def _render_default(self, textdata):
return textdata
def _render_mako(self, textdata):
""" render textdata passed as argument using mako
Returns rendered textdata """
if not self.tclass:
return textdata
self.logger.info('template processing on interfaces file ...')
t = self.tclass(text=textdata, output_encoding='utf-8',
lookup=self.tclassargs.get('lookup'))
return t.render()

52
ifupdown/utils.py Normal file
View File

@@ -0,0 +1,52 @@
#!/usr/bin/python
#
# Copyright 2014 Cumulus Networks, Inc. All rights reserved.
# Author: Roopa Prabhu, roopa@cumulusnetworks.com
#
# utils --
# helper class
#
import os
import fcntl
import re
class utils():
@classmethod
def importName(cls, modulename, name):
""" Import a named object """
try:
module = __import__(modulename, globals(), locals(), [name])
except ImportError:
return None
return getattr(module, name)
@classmethod
def lockFile(cls, lockfile):
try:
fp = os.open(lockfile, os.O_CREAT | os.O_TRUNC | os.O_WRONLY)
fcntl.flock(fp, fcntl.LOCK_EX | fcntl.LOCK_NB)
except IOError:
return False
return True
@classmethod
def parse_iface_range(cls, name):
range_match = re.match("^([\w\.]+)\[([\d]+)-([\d]+)\]", name)
if range_match:
range_groups = range_match.groups()
if range_groups[1] and range_groups[2]:
return (range_groups[0], int(range_groups[1], 10),
int(range_groups[2], 10))
return None
@classmethod
def expand_iface_range(cls, name):
ifacenames = []
iface_range = cls.parse_iface_range(name)
if iface_range:
for i in range(iface_range[1], iface_range[2]):
ifacenames.append('%s-%d' %(iface_range[0], i))
return ifacenames