From f82758bf2cbb5b4c04cfb953698f26b2d15668f0 Mon Sep 17 00:00:00 2001 From: Roopa Prabhu Date: Fri, 6 Mar 2015 21:46:10 -0800 Subject: [PATCH] Merge 'vlan filtering bridge + vxlan + mlag + vrr' support from internal tree to external This also combines python-ifupdown2 and python-ifupdown2-addons package into a single python-ifupdown2 package --- ifupdown2/README.rst | 14 +- ifupdown2/addons/address.py | 393 +++++ ifupdown2/addons/addressvirtual.py | 321 ++++ ifupdown2/addons/bridge.py | 1501 +++++++++++++++++ ifupdown2/addons/bridgevlan.py | 170 ++ ifupdown2/addons/dhcp.py | 129 ++ ifupdown2/addons/ethtool.py | 133 ++ ifupdown2/addons/ifenslave.py | 438 +++++ ifupdown2/addons/loopback.py | 63 + ifupdown2/addons/mstpctl.py | 770 +++++++++ ifupdown2/addons/usercmds.py | 97 ++ ifupdown2/addons/vlan.py | 236 +++ ifupdown2/addons/vrrpd.py | 167 ++ ifupdown2/addons/vxlan.py | 158 ++ ifupdown2/config/addons.conf | 28 + ifupdown2/config/ifupdown2.conf | 29 + ifupdown2/config/networking | 17 + ifupdown2/debian/python-ifupdown2.postinst | 8 +- ifupdown2/docs.addons/Makefile | 153 ++ ifupdown2/docs.addons/source/addonsapiref.rst | 61 + .../docs.addons/source/addonshelperapiref.rst | 44 + ifupdown2/docs.addons/source/conf.py | 247 +++ .../docs.addons/source/developmentcorner.rst | 58 + .../docs.addons/source/gettingstarted.rst | 29 + ifupdown2/docs.addons/source/index.rst | 25 + ifupdown2/docs.addons/source/intro.rst | 21 + .../docs/examples/interfaces_bridge_igmp_mstp | 3 + .../vlan_aware_bridges/interfaces.basic | 25 + .../interfaces.vlan_prune_and_access_ports | 59 + .../vlan_aware_bridges/interfaces.with_bonds | 92 + .../vlan_aware_bridges/interfaces.with_clag | 87 + ifupdown2/ifupdown/iface.py | 128 +- ifupdown2/ifupdown/iff.py | 36 + ifupdown2/ifupdown/ifupdownbase.py | 5 +- ifupdown2/ifupdown/ifupdownmain.py | 439 ++++- ifupdown2/ifupdown/netlink.py | 241 +++ ifupdown2/ifupdown/networkinterfaces.py | 107 +- ifupdown2/ifupdown/rtnetlink.py | 860 ++++++++++ ifupdown2/ifupdown/rtnetlink_api.py | 237 +++ ifupdown2/ifupdown/scheduler.py | 151 +- ifupdown2/ifupdown/statemanager.py | 3 +- ifupdown2/ifupdown/utils.py | 20 + ifupdown2/ifupdownaddons/__init__.py | 0 ifupdown2/ifupdownaddons/bridgeutils.py | 501 ++++++ ifupdown2/ifupdownaddons/cache.py | 90 + ifupdown2/ifupdownaddons/dhclient.py | 86 + ifupdown2/ifupdownaddons/ifenslaveutil.py | 421 +++++ ifupdown2/ifupdownaddons/iproute2.py | 704 ++++++++ ifupdown2/ifupdownaddons/modulebase.py | 363 ++++ ifupdown2/ifupdownaddons/mstpctlutil.py | 171 ++ ifupdown2/ifupdownaddons/utilsbase.py | 163 ++ ifupdown2/init.d/networking | 76 +- ifupdown2/man.rst/ifquery.8.rst | 7 +- ifupdown2/man.rst/ifreload.8.rst | 15 +- ifupdown2/man.rst/ifup.8.rst | 10 +- .../man.rst/ifupdown-addons-interfaces.5.rst | 1079 ++++++++++++ ifupdown2/man/ifquery.8 | 230 +++ ifupdown2/man/ifreload.8 | 116 ++ ifupdown2/man/ifup.8 | 269 +++ ifupdown2/man/ifupdown-addons-interfaces.5 | 1343 +++++++++++++++ ifupdown2/man/interfaces.5 | 207 +++ ifupdown2/sbin/ifupdown | 106 +- ifupdown2/setup.py | 27 +- 63 files changed, 13557 insertions(+), 230 deletions(-) create mode 100644 ifupdown2/addons/address.py create mode 100644 ifupdown2/addons/addressvirtual.py create mode 100644 ifupdown2/addons/bridge.py create mode 100644 ifupdown2/addons/bridgevlan.py create mode 100644 ifupdown2/addons/dhcp.py create mode 100644 ifupdown2/addons/ethtool.py create mode 100644 ifupdown2/addons/ifenslave.py create mode 100644 ifupdown2/addons/loopback.py create mode 100644 ifupdown2/addons/mstpctl.py create mode 100644 ifupdown2/addons/usercmds.py create mode 100644 ifupdown2/addons/vlan.py create mode 100644 ifupdown2/addons/vrrpd.py create mode 100644 ifupdown2/addons/vxlan.py create mode 100644 ifupdown2/config/addons.conf create mode 100644 ifupdown2/config/networking create mode 100644 ifupdown2/docs.addons/Makefile create mode 100644 ifupdown2/docs.addons/source/addonsapiref.rst create mode 100644 ifupdown2/docs.addons/source/addonshelperapiref.rst create mode 100644 ifupdown2/docs.addons/source/conf.py create mode 100644 ifupdown2/docs.addons/source/developmentcorner.rst create mode 100644 ifupdown2/docs.addons/source/gettingstarted.rst create mode 100644 ifupdown2/docs.addons/source/index.rst create mode 100644 ifupdown2/docs.addons/source/intro.rst create mode 100644 ifupdown2/docs/examples/vlan_aware_bridges/interfaces.basic create mode 100644 ifupdown2/docs/examples/vlan_aware_bridges/interfaces.vlan_prune_and_access_ports create mode 100644 ifupdown2/docs/examples/vlan_aware_bridges/interfaces.with_bonds create mode 100644 ifupdown2/docs/examples/vlan_aware_bridges/interfaces.with_clag create mode 100644 ifupdown2/ifupdown/iff.py create mode 100644 ifupdown2/ifupdown/netlink.py create mode 100644 ifupdown2/ifupdown/rtnetlink.py create mode 100644 ifupdown2/ifupdown/rtnetlink_api.py create mode 100644 ifupdown2/ifupdownaddons/__init__.py create mode 100644 ifupdown2/ifupdownaddons/bridgeutils.py create mode 100644 ifupdown2/ifupdownaddons/cache.py create mode 100644 ifupdown2/ifupdownaddons/dhclient.py create mode 100644 ifupdown2/ifupdownaddons/ifenslaveutil.py create mode 100644 ifupdown2/ifupdownaddons/iproute2.py create mode 100644 ifupdown2/ifupdownaddons/modulebase.py create mode 100644 ifupdown2/ifupdownaddons/mstpctlutil.py create mode 100644 ifupdown2/ifupdownaddons/utilsbase.py create mode 100644 ifupdown2/man.rst/ifupdown-addons-interfaces.5.rst create mode 100644 ifupdown2/man/ifquery.8 create mode 100644 ifupdown2/man/ifreload.8 create mode 100644 ifupdown2/man/ifup.8 create mode 100644 ifupdown2/man/ifupdown-addons-interfaces.5 create mode 100644 ifupdown2/man/interfaces.5 diff --git a/ifupdown2/README.rst b/ifupdown2/README.rst index 8ab8252..6187ec0 100644 --- a/ifupdown2/README.rst +++ b/ifupdown2/README.rst @@ -9,11 +9,11 @@ The python-ifupdown2 package provides the infrastructure for parsing /etc/network/interfaces file, loading, scheduling and state management of interfaces. -It dynamically loads python modules from /usr/share/ifupdownmodules (provided - by the python-ifupdown2-addons package). To remain compatible with other -packages that depend on ifupdown, it also executes scripts under /etc/network/. +It dynamically loads python modules from /usr/share/ifupdownaddons. +To remain compatible with other packages that depend on ifupdown, +it also executes scripts under /etc/network/. To make the transition smoother, a python module under -/usr/share/ifupdownmodules will override a script by the same name under +/usr/share/ifupdownaddons will override a script by the same name under /etc/network/. It publishes an interface object which is passed to all loadble python @@ -26,7 +26,7 @@ pluggable python modules: Unlike original ifupdown, all interface configuration is moved to external python modules. That includes inet, inet6 and dhcp configurations. -A set of default modules are provided by the python-ifupdown2-addons deb. +A set of default modules are included in the package. python-ifupdown2 expects a few things from the pluggable modules: - the module should implement a class by the same name @@ -72,7 +72,3 @@ install - or install from deb dpkg -i python-ifupdown2-.deb - -- note that python-ifupdown2 requires python-ifupdown2-addons package to - function. And python-ifupdown2-addons deb has an install dependency on - python-ifupdown2 diff --git a/ifupdown2/addons/address.py b/ifupdown2/addons/address.py new file mode 100644 index 0000000..627837e --- /dev/null +++ b/ifupdown2/addons/address.py @@ -0,0 +1,393 @@ +#!/usr/bin/python +# +# Copyright 2014 Cumulus Networks, Inc. All rights reserved. +# Author: Roopa Prabhu, roopa@cumulusnetworks.com +# + +try: + from ipaddr import IPNetwork + from sets import Set + from ifupdown.iface import * + from ifupdownaddons.modulebase import moduleBase + from ifupdownaddons.iproute2 import iproute2 + from ifupdownaddons.dhclient import dhclient +except ImportError, e: + raise ImportError (str(e) + "- required module not found") + +class address(moduleBase): + """ ifupdown2 addon module to configure address, mtu, hwaddress, alias + (description) on an interface """ + + _modinfo = {'mhelp' : 'address configuration module for interfaces', + 'attrs': { + 'address' : + {'help' : 'ipv4 or ipv6 addresses', + 'example' : ['address 10.0.12.3/24', + 'address 2000:1000:1000:1000:3::5/128']}, + 'netmask' : + {'help': 'netmask', + 'example' : ['netmask 255.255.255.0'], + 'compat' : True}, + 'broadcast' : + {'help': 'broadcast address', + 'example' : ['broadcast 10.0.1.255']}, + 'scope' : + {'help': 'scope', + 'example' : ['scope host']}, + 'preferred-lifetime' : + {'help': 'preferred lifetime', + 'example' : ['preferred-lifetime forever', + 'preferred-lifetime 10']}, + 'gateway' : + {'help': 'default gateway', + 'example' : ['gateway 255.255.255.0']}, + 'mtu' : + { 'help': 'interface mtu', + 'example' : ['mtu 1600'], + 'default' : '1500'}, + 'hwaddress' : + {'help' : 'hw address', + 'example': ['hwaddress 44:38:39:00:27:b8']}, + 'alias' : + { 'help': 'description/alias', + 'example' : ['alias testnetwork']}, + 'address-purge' : + { 'help': 'purge existing addresses. By default ' + + 'any existing ip addresses on an interface are ' + + 'purged to match persistant addresses in the ' + + 'interfaces file. Set this attribute to \'no\'' + + 'if you want to preserve existing addresses', + 'default' : 'yes', + 'example' : ['address-purge yes/no']}}} + + def __init__(self, *args, **kargs): + moduleBase.__init__(self, *args, **kargs) + self.ipcmd = None + self._bridge_fdb_query_cache = {} + + def _address_valid(self, addrs): + if not addrs: + return False + if any(map(lambda a: True if a[:7] != '0.0.0.0' + else False, addrs)): + return True + return False + + def _process_bridge(self, ifaceobj, up): + hwaddress = ifaceobj.get_attr_value_first('hwaddress') + addrs = ifaceobj.get_attr_value_first('address') + is_vlan_dev_on_vlan_aware_bridge = False + is_bridge = self.ipcmd.is_bridge(ifaceobj.name) + if not is_bridge: + if '.' in ifaceobj.name: + (bridgename, vlan) = ifaceobj.name.split('.') + is_vlan_dev_on_vlan_aware_bridge = self.ipcmd.bridge_is_vlan_aware(bridgename) + if ((is_bridge and not self.ipcmd.bridge_is_vlan_aware(ifaceobj.name)) + or is_vlan_dev_on_vlan_aware_bridge): + if self._address_valid(addrs): + if up: + self.write_file('/proc/sys/net/ipv4/conf/%s' %ifaceobj.name + + '/arp_accept', '1') + else: + self.write_file('/proc/sys/net/ipv4/conf/%s' %ifaceobj.name + + '/arp_accept', '0') + if hwaddress and is_vlan_dev_on_vlan_aware_bridge: + if up: + self.ipcmd.bridge_fdb_add(bridgename, hwaddress, vlan) + else: + self.ipcmd.bridge_fdb_del(bridgename, hwaddress, vlan) + + def _inet_address_config(self, ifaceobj): + purge_addresses = ifaceobj.get_attr_value_first('address-purge') + if not purge_addresses: + purge_addresses = 'yes' + newaddrs = [] + addrs = ifaceobj.get_attr_value('address') + if addrs: + # If user address is not in CIDR notation, convert them to CIDR + for addr_index in range(0, len(addrs)): + addr = addrs[addr_index] + if '/' in addr: + newaddrs.append(addr) + continue + netmask = ifaceobj.get_attr_value_n('netmask', addr_index) + if netmask: + prefixlen = IPNetwork('%s' %addr + + '/%s' %netmask).prefixlen + newaddrs.append(addr + '/%s' %prefixlen) + else: + newaddrs.append(addr) + + if (not self.PERFMODE and + not (ifaceobj.flags & iface.HAS_SIBLINGS) and + purge_addresses == 'yes'): + # if perfmode is not set and also if iface has no sibling + # objects, purge addresses that are not present in the new + # config + runningaddrs = self.ipcmd.addr_get(ifaceobj.name, details=False) + if newaddrs == runningaddrs: + return + try: + # if primary address is not same, there is no need to keep any. + # reset all addresses + if (newaddrs and runningaddrs and + (newaddrs[0] != runningaddrs[0])): + self.ipcmd.del_addr_all(ifaceobj.name) + else: + self.ipcmd.del_addr_all(ifaceobj.name, newaddrs) + except Exception, e: + self.log_warn(str(e)) + if not newaddrs: + return + for addr_index in range(0, len(newaddrs)): + try: + self.ipcmd.addr_add(ifaceobj.name, newaddrs[addr_index], + ifaceobj.get_attr_value_n('broadcast', addr_index), + ifaceobj.get_attr_value_n('pointopoint',addr_index), + ifaceobj.get_attr_value_n('scope', addr_index), + ifaceobj.get_attr_value_n('preferred-lifetime', addr_index)) + except Exception, e: + self.log_error(str(e)) + + def _up(self, ifaceobj): + if not self.ipcmd.link_exists(ifaceobj.name): + return + addr_method = ifaceobj.addr_method + try: + # release any stale dhcp addresses if present + if (addr_method != "dhcp" and not self.PERFMODE and + not (ifaceobj.flags & iface.HAS_SIBLINGS)): + # if not running in perf mode and ifaceobj does not have + # any sibling iface objects, kill any stale dhclient + # processes + dhclientcmd = dhclient() + if dhclient.is_running(ifaceobj.name): + # release any dhcp leases + dhclientcmd.release(ifaceobj.name) + elif dhclient.is_running6(ifaceobj.name): + dhclientcmd.release6(ifaceobj.name) + except: + pass + + self.ipcmd.batch_start() + if addr_method != "dhcp": + self._inet_address_config(ifaceobj) + mtu = ifaceobj.get_attr_value_first('mtu') + if mtu: + self.ipcmd.link_set(ifaceobj.name, 'mtu', mtu) + alias = ifaceobj.get_attr_value_first('alias') + if alias: + self.ipcmd.link_set_alias(ifaceobj.name, alias) + hwaddress = ifaceobj.get_attr_value_first('hwaddress') + if hwaddress: + self.ipcmd.link_set(ifaceobj.name, 'address', hwaddress) + self.ipcmd.batch_commit() + + try: + # Handle special things on a bridge + self._process_bridge(ifaceobj, True) + except Exception, e: + self.log_warn('%s: %s' %(ifaceobj.name, str(e))) + pass + + if addr_method != "dhcp": + self.ipcmd.route_add_gateway(ifaceobj.name, + ifaceobj.get_attr_value_first('gateway')) + + def _down(self, ifaceobj): + try: + if not self.ipcmd.link_exists(ifaceobj.name): + return + addr_method = ifaceobj.addr_method + if addr_method != "dhcp": + self.ipcmd.route_del_gateway(ifaceobj.name, + ifaceobj.get_attr_value_first('gateway'), + ifaceobj.get_attr_value_first('metric')) + self.ipcmd.del_addr_all(ifaceobj.name) + mtu = ifaceobj.get_attr_value_first('mtu') + if mtu: + self.ipcmd.link_set(ifaceobj.name, 'mtu', + self.get_mod_subattr('mtu', 'default')) + alias = ifaceobj.get_attr_value_first('alias') + if alias: + self.ipcmd.link_set(ifaceobj.name, 'alias', "\'\'") + # XXX hwaddress reset cannot happen because we dont know last + # address. + + # Handle special things on a bridge + self._process_bridge(ifaceobj, False) + except Exception, e: + self.logger.debug('%s : %s' %(ifaceobj.name, str(e))) + pass + + def _get_iface_addresses(self, ifaceobj): + addrlist = ifaceobj.get_attr_value('address') + outaddrlist = [] + + if not addrlist: return None + for addrindex in range(0, len(addrlist)): + addr = addrlist[addrindex] + netmask = ifaceobj.get_attr_value_n('netmask', addrindex) + if netmask: + prefixlen = IPNetwork('%s' %addr + + '/%s' %netmask).prefixlen + addr = addr + '/%s' %prefixlen + outaddrlist.append(addr) + return outaddrlist + + def _get_bridge_fdbs(self, bridgename, vlan): + fdbs = self._bridge_fdb_query_cache.get(bridgename) + if not fdbs: + fdbs = self.ipcmd.bridge_fdb_show_dev(bridgename) + if not fdbs: + return + self._bridge_fdb_query_cache[bridgename] = fdbs + return fdbs.get(vlan) + + def _check_addresses_in_bridge(self, ifaceobj, hwaddress): + """ If the device is a bridge, make sure the addresses + are in the bridge """ + if '.' in ifaceobj.name: + (bridgename, vlan) = ifaceobj.name.split('.') + if self.ipcmd.bridge_is_vlan_aware(bridgename): + fdb_addrs = self._get_bridge_fdbs(bridgename, vlan) + if not fdb_addrs or hwaddress not in fdb_addrs: + return False + return True + + def _query_check(self, ifaceobj, ifaceobjcurr): + runningaddrsdict = None + if not self.ipcmd.link_exists(ifaceobj.name): + self.logger.debug('iface %s not found' %ifaceobj.name) + return + addr_method = ifaceobj.addr_method + self.query_n_update_ifaceobjcurr_attr(ifaceobj, ifaceobjcurr, + 'mtu', self.ipcmd.link_get_mtu) + hwaddress = ifaceobj.get_attr_value_first('hwaddress') + if hwaddress: + rhwaddress = self.ipcmd.link_get_hwaddress(ifaceobj.name) + if not rhwaddress or rhwaddress != hwaddress: + ifaceobjcurr.update_config_with_status('hwaddress', rhwaddress, + 1) + elif not self._check_addresses_in_bridge(ifaceobj, hwaddress): + # XXX: hw address is not in bridge + ifaceobjcurr.update_config_with_status('hwaddress', rhwaddress, + 1) + ifaceobjcurr.status_str = 'bridge fdb error' + else: + ifaceobjcurr.update_config_with_status('hwaddress', rhwaddress, + 0) + self.query_n_update_ifaceobjcurr_attr(ifaceobj, ifaceobjcurr, + 'alias', self.ipcmd.link_get_alias) + # compare addresses + if addr_method == 'dhcp': + return + addrs = self._get_iface_addresses(ifaceobj) + runningaddrsdict = self.ipcmd.addr_get(ifaceobj.name) + + # Set ifaceobjcurr method and family + ifaceobjcurr.addr_method = ifaceobj.addr_method + ifaceobjcurr.addr_family = ifaceobj.addr_family + if not runningaddrsdict and not addrs: + return + runningaddrs = runningaddrsdict.keys() if runningaddrsdict else [] + if runningaddrs != addrs: + runningaddrsset = set(runningaddrs) if runningaddrs else set([]) + addrsset = set(addrs) if addrs else set([]) + if (ifaceobj.flags & iface.HAS_SIBLINGS): + if not addrsset: + return + # only check for addresses present in running config + addrsdiff = addrsset.difference(runningaddrsset) + for addr in addrs: + if addr in addrsdiff: + ifaceobjcurr.update_config_with_status('address', + addr, 1) + else: + ifaceobjcurr.update_config_with_status('address', + addr, 0) + else: + addrsdiff = addrsset.symmetric_difference(runningaddrsset) + for addr in addrsset.union(runningaddrsset): + if addr in addrsdiff: + ifaceobjcurr.update_config_with_status('address', + addr, 1) + else: + ifaceobjcurr.update_config_with_status('address', + addr, 0) + elif addrs: + [ifaceobjcurr.update_config_with_status('address', + addr, 0) for addr in addrs] + #XXXX Check broadcast address, scope, etc + return + + def _query_running(self, ifaceobjrunning): + if not self.ipcmd.link_exists(ifaceobjrunning.name): + self.logger.debug('iface %s not found' %ifaceobjrunning.name) + return + dhclientcmd = dhclient() + if (dhclientcmd.is_running(ifaceobjrunning.name) or + dhclientcmd.is_running6(ifaceobjrunning.name)): + # If dhcp is configured on the interface, we skip it + return + isloopback = self.ipcmd.link_isloopback(ifaceobjrunning.name) + if isloopback: + default_addrs = ['127.0.0.1/8', '::1/128'] + ifaceobjrunning.addr_family = 'inet' + ifaceobjrunning.addr_method = 'loopback' + else: + default_addrs = [] + runningaddrsdict = self.ipcmd.addr_get(ifaceobjrunning.name) + if runningaddrsdict: + [ifaceobjrunning.update_config('address', addr) + for addr, addrattrs in runningaddrsdict.items() + if addr not in default_addrs] + mtu = self.ipcmd.link_get_mtu(ifaceobjrunning.name) + if (mtu and + (ifaceobjrunning.name == 'lo' and mtu != '16436') or + (ifaceobjrunning.name != 'lo' and + mtu != self.get_mod_subattr('mtu', 'default'))): + ifaceobjrunning.update_config('mtu', mtu) + alias = self.ipcmd.link_get_alias(ifaceobjrunning.name) + if alias: + ifaceobjrunning.update_config('alias', alias) + + _run_ops = {'up' : _up, + 'down' : _down, + 'query-checkcurr' : _query_check, + 'query-running' : _query_running } + + def get_ops(self): + """ returns list of ops supported by this module """ + return self._run_ops.keys() + + def _init_command_handlers(self): + if not self.ipcmd: + self.ipcmd = iproute2(**self.get_flags()) + + def run(self, ifaceobj, operation, query_ifaceobj=None, **extra_args): + """ run address configuration on the interface object passed as argument + + Args: + **ifaceobj** (object): iface object + + **operation** (str): any of 'up', 'down', 'query-checkcurr', + 'query-running' + Kwargs: + query_ifaceobj (object): query check ifaceobject. This is only + valid when op is 'query-checkcurr'. It is an object same as + ifaceobj, but contains running attribute values and its config + status. The modules can use it to return queried running state + of interfaces. status is success if the running state is same + as user required state in ifaceobj. error otherwise. + """ + if ifaceobj.type == ifaceType.BRIDGE_VLAN: + return + op_handler = self._run_ops.get(operation) + if not op_handler: + return + self._init_command_handlers() + if operation == 'query-checkcurr': + op_handler(self, ifaceobj, query_ifaceobj) + else: + op_handler(self, ifaceobj) diff --git a/ifupdown2/addons/addressvirtual.py b/ifupdown2/addons/addressvirtual.py new file mode 100644 index 0000000..98ac536 --- /dev/null +++ b/ifupdown2/addons/addressvirtual.py @@ -0,0 +1,321 @@ +#!/usr/bin/python +# +# Copyright 2014 Cumulus Networks, Inc. All rights reserved. +# Author: Roopa Prabhu, roopa@cumulusnetworks.com +# + +from ifupdown.iface import * +from ifupdownaddons.modulebase import moduleBase +from ifupdownaddons.iproute2 import iproute2 +import ifupdown.rtnetlink_api as rtnetlink_api +from ipaddr import IPNetwork +import logging +import os +import glob + +class addressvirtual(moduleBase): + """ ifupdown2 addon module to configure virtual addresses """ + + _modinfo = {'mhelp' : 'address module configures virtual addresses for ' + + 'interfaces. It creates a macvlan interface for ' + + 'every mac ip address-virtual line', + 'attrs' : { + 'address-virtual' : + { 'help' : 'bridge router virtual mac and ip', + 'example' : ['address-virtual 00:11:22:33:44:01 11.0.1.254/24 11.0.1.254/24']} + }} + + + def __init__(self, *args, **kargs): + moduleBase.__init__(self, *args, **kargs) + self.ipcmd = None + self._bridge_fdb_query_cache = {} + + def _is_supported(self, ifaceobj): + if ifaceobj.get_attr_value_first('address-virtual'): + return True + return False + + def _get_macvlan_prefix(self, ifaceobj): + return '%s-v' %ifaceobj.name[0:13].replace('.', '-') + + def _add_addresses_to_bridge(self, ifaceobj, hwaddress): + # XXX: batch the addresses + if '.' in ifaceobj.name: + (bridgename, vlan) = ifaceobj.name.split('.') + if self.ipcmd.bridge_is_vlan_aware(bridgename): + [self.ipcmd.bridge_fdb_add(bridgename, addr, + vlan) for addr in hwaddress] + elif self.ipcmd.is_bridge(ifaceobj.name): + [self.ipcmd.bridge_fdb_add(ifaceobj.name, addr) + for addr in hwaddress] + + def _remove_addresses_from_bridge(self, ifaceobj, hwaddress): + # XXX: batch the addresses + bridgename = None + if '.' in ifaceobj.name: + if self.ipcmd.bridge_is_vlan_aware(bridgename): + (bridgename, vlan) = ifaceobj.name.split('.') + elif self.ipcmd.is_bridge(ifaceobj.name): + vlan = None + bridgename = ifaceobj.name + if not bridgename: + return + for addr in hwaddress: + try: + self.ipcmd.bridge_fdb_del(bridgename, addr, vlan) + except Exception, e: + self.logger.debug("%s: %s" %(ifaceobj.name, str(e))) + pass + + def _get_bridge_fdbs(self, bridgename, vlan): + fdbs = self._bridge_fdb_query_cache.get(bridgename) + if not fdbs: + fdbs = self.ipcmd.bridge_fdb_show_dev(bridgename) + if not fdbs: + return + self._bridge_fdb_query_cache[bridgename] = fdbs + return fdbs.get(vlan) + + def _check_addresses_in_bridge(self, ifaceobj, hwaddress): + """ If the device is a bridge, make sure the addresses + are in the bridge """ + if '.' in ifaceobj.name: + (bridgename, vlan) = ifaceobj.name.split('.') + if self.ipcmd.bridge_is_vlan_aware(bridgename): + fdb_addrs = self._get_bridge_fdbs(bridgename, vlan) + if not fdb_addrs or hwaddress not in fdb_addrs: + return False + return True + + def _fix_connected_route(self, ifaceobj, vifacename, addr): + # + # XXX: Hack to make sure the primary address + # is the first in the routing table. + # + # We use `ip route get` on the vrr network to see which + # device the kernel returns. if it is the mac vlan device, + # flap the macvlan device to adjust the routing table entry. + # + # flapping the macvlan device makes sure the macvlan + # connected route goes through delete + add, hence adjusting + # the order in the routing table. + # + try: + self.logger.info('%s: checking route entry ...' %ifaceobj.name) + ip = IPNetwork(addr) + route_prefix = '%s/%d' %(ip.network, ip.prefixlen) + + dev = self.ipcmd.ip_route_get_dev(route_prefix) + if dev and dev == vifacename: + self.logger.info('%s: preferred routing entry ' %ifaceobj.name + + 'seems to be of the macvlan dev %s' + %vifacename + + ' .. flapping macvlan dev to fix entry.') + self.ipcmd.link_down(vifacename) + self.ipcmd.link_up(vifacename) + except Exception, e: + self.logger.debug('%s: fixing route entry failed (%s)' + %str(e)) + pass + + def _apply_address_config(self, ifaceobj, address_virtual_list): + purge_existing = False if self.PERFMODE else True + + hwaddress = [] + self.ipcmd.batch_start() + av_idx = 0 + macvlan_prefix = self._get_macvlan_prefix(ifaceobj) + for av in address_virtual_list: + av_attrs = av.split() + if len(av_attrs) < 2: + self.logger.warn("%s: incorrect address-virtual attrs '%s'" + %(ifaceobj.name, av)) + av_idx += 1 + continue + + # Create a macvlan device on this device and set the virtual + # router mac and ip on it + link_created = False + macvlan_ifacename = '%s%d' %(macvlan_prefix, av_idx) + if not self.ipcmd.link_exists(macvlan_ifacename): + rtnetlink_api.rtnl_api.create_macvlan(macvlan_ifacename, + ifaceobj.name) + link_created = True + if av_attrs[0] != 'None': + self.ipcmd.link_set_hwaddress(macvlan_ifacename, av_attrs[0]) + hwaddress.append(av_attrs[0]) + self.ipcmd.addr_add_multiple(macvlan_ifacename, av_attrs[1:], + purge_existing) + # If link existed before, flap the link + if not link_created: + self._fix_connected_route(ifaceobj, macvlan_ifacename, + av_attrs[1]) + av_idx += 1 + self.ipcmd.batch_commit() + + # if ifaceobj is a bridge and bridge is a vlan aware bridge + # add the vid to the bridge + self._add_addresses_to_bridge(ifaceobj, hwaddress) + + def _remove_running_address_config(self, ifaceobj): + if not self.ipcmd.link_exists(ifaceobj.name): + return + hwaddress = [] + self.ipcmd.batch_start() + macvlan_prefix = self._get_macvlan_prefix(ifaceobj) + for macvlan_ifacename in glob.glob("/sys/class/net/%s-*" %macvlan_prefix): + macvlan_ifacename = os.path.basename(macvlan_ifacename) + if not self.ipcmd.link_exists(macvlan_ifacename): + continue + hwaddress.append(self.ipcmd.link_get_hwaddress(macvlan_ifacename)) + self.ipcmd.link_delete(os.path.basename(macvlan_ifacename)) + # XXX: Also delete any fdb addresses. This requires, checking mac address + # on individual macvlan interfaces and deleting the vlan from that. + self.ipcmd.batch_commit() + if any(hwaddress): + self._remove_addresses_from_bridge(ifaceobj, hwaddress) + + def _remove_address_config(self, ifaceobj, address_virtual_list=None): + if not address_virtual_list: + self._remove_running_address_config(ifaceobj) + return + + if not self.ipcmd.link_exists(ifaceobj.name): + return + hwaddress = [] + self.ipcmd.batch_start() + av_idx = 0 + macvlan_prefix = self._get_macvlan_prefix(ifaceobj) + for av in address_virtual_list: + av_attrs = av.split() + if len(av_attrs) < 2: + self.logger.warn("%s: incorrect address-virtual attrs '%s'" + %(ifaceobj.name, av)) + av_idx += 1 + continue + + # Delete the macvlan device on this device + macvlan_ifacename = '%s%d' %(macvlan_prefix, av_idx) + self.ipcmd.link_delete(os.path.basename(macvlan_ifacename)) + if av_attrs[0] != 'None': + hwaddress.append(av_attrs[0]) + av_idx += 1 + self.ipcmd.batch_commit() + self._remove_addresses_from_bridge(ifaceobj, hwaddress) + + def _up(self, ifaceobj): + address_virtual_list = ifaceobj.get_attr_value('address-virtual') + if not address_virtual_list: + # XXX: address virtual is not present. In which case, + # delete stale macvlan devices. + self._remove_address_config(ifaceobj, address_virtual_list) + return + + if not self.ipcmd.link_exists(ifaceobj.name): + return + self._apply_address_config(ifaceobj, address_virtual_list) + + def _down(self, ifaceobj): + try: + self._remove_address_config(ifaceobj, + ifaceobj.get_attr_value('address-virtual')) + except Exception, e: + self.log_warn(str(e)) + + def _query_check(self, ifaceobj, ifaceobjcurr): + address_virtual_list = ifaceobj.get_attr_value('address-virtual') + if not address_virtual_list: + return + if not self.ipcmd.link_exists(ifaceobj.name): + return + av_idx = 0 + macvlan_prefix = self._get_macvlan_prefix(ifaceobj) + for address_virtual in address_virtual_list: + av_attrs = address_virtual.split() + if len(av_attrs) < 2: + self.logger.warn("%s: incorrect address-virtual attrs '%s'" + %(ifaceobj.name, address_virtual)) + av_idx += 1 + continue + + # Check if the macvlan device on this interface + macvlan_ifacename = '%s%d' %(macvlan_prefix, av_idx) + if not self.ipcmd.link_exists(macvlan_ifacename): + ifaceobjcurr.update_config_with_status('address-virtual', + '', 1) + av_idx += 1 + continue + # Check mac and ip address + rhwaddress = self.ipcmd.link_get_hwaddress(macvlan_ifacename) + raddrs = self.ipcmd.addr_get(macvlan_ifacename) + if not raddrs or not rhwaddress: + ifaceobjcurr.update_config_with_status('address-virtual', '', 1) + av_idx += 1 + continue + raddrs = raddrs.keys() + if (rhwaddress == av_attrs[0] and raddrs == av_attrs[1:] and + self._check_addresses_in_bridge(ifaceobj, av_attrs[0])): + ifaceobjcurr.update_config_with_status('address-virtual', + address_virtual, 0) + else: + raddress_virtual = '%s %s' %(rhwaddress, ' '.join(raddrs)) + ifaceobjcurr.update_config_with_status('address-virtual', + raddress_virtual, 1) + av_idx += 1 + return + + def _query_running(self, ifaceobjrunning): + macvlan_prefix = self._get_macvlan_prefix(ifaceobjrunning) + address_virtuals = glob.glob("/sys/class/net/%s*" %macvlan_prefix) + for av in address_virtuals: + macvlan_ifacename = os.path.basename(av) + rhwaddress = self.ipcmd.link_get_hwaddress(macvlan_ifacename) + raddress = self.ipcmd.addr_get(macvlan_ifacename) + if not raddress: + self.logger.warn('%s: no running addresses' + %ifaceobjrunning.name) + raddress = [] + ifaceobjrunning.update_config('address-virtual', + '%s %s' %(rhwaddress, ''.join(raddress))) + return + + _run_ops = {'up' : _up, + 'down' : _down, + 'query-checkcurr' : _query_check, + 'query-running' : _query_running} + + def get_ops(self): + """ returns list of ops supported by this module """ + return self._run_ops.keys() + + def _init_command_handlers(self): + if not self.ipcmd: + self.ipcmd = iproute2(**self.get_flags()) + + def run(self, ifaceobj, operation, query_ifaceobj=None, **extra_args): + """ run vlan configuration on the interface object passed as argument + + Args: + **ifaceobj** (object): iface object + + **operation** (str): any of 'pre-up', 'post-down', 'query-checkcurr', + 'query-running' + Kwargs: + **query_ifaceobj** (object): query check ifaceobject. This is only + valid when op is 'query-checkcurr'. It is an object same as + ifaceobj, but contains running attribute values and its config + status. The modules can use it to return queried running state + of interfaces. status is success if the running state is same + as user required state in ifaceobj. error otherwise. + """ + if ifaceobj.type == ifaceType.BRIDGE_VLAN: + return + op_handler = self._run_ops.get(operation) + if not op_handler: + return + self._init_command_handlers() + if operation == 'query-checkcurr': + op_handler(self, ifaceobj, query_ifaceobj) + else: + op_handler(self, ifaceobj) diff --git a/ifupdown2/addons/bridge.py b/ifupdown2/addons/bridge.py new file mode 100644 index 0000000..ebc9fbf --- /dev/null +++ b/ifupdown2/addons/bridge.py @@ -0,0 +1,1501 @@ +#!/usr/bin/python +# +# Copyright 2014 Cumulus Networks, Inc. All rights reserved. +# Author: Roopa Prabhu, roopa@cumulusnetworks.com +# + +from sets import Set +from ifupdown.iface import * +from ifupdownaddons.modulebase import moduleBase +from ifupdownaddons.bridgeutils import brctl +from ifupdownaddons.iproute2 import iproute2 +from collections import Counter +import ifupdown.rtnetlink_api as rtnetlink_api +import itertools +import re +import time + +class bridge(moduleBase): + """ ifupdown2 addon module to configure linux bridges """ + + _modinfo = { 'mhelp' : 'Bridge configuration module. Supports both ' + + 'vlan aware and non vlan aware bridges. For the vlan ' + + 'aware bridge, the port specific attributes must be ' + + 'specified under the port. And for vlan unaware bridge ' + + 'port specific attributes must be specified under the ' + + 'bridge.', + 'attrs' : { + 'bridge-vlan-aware' : + {'help' : 'vlan aware bridge. Setting this ' + + 'attribute to yes enables vlan filtering' + + ' on the bridge', + 'example' : ['bridge-vlan-aware yes/no']}, + 'bridge-ports' : + {'help' : 'bridge ports', + 'required' : True, + 'example' : ['bridge-ports swp1.100 swp2.100 swp3.100', + 'bridge-ports glob swp1-3.100', + 'bridge-ports regex (swp[1|2|3].100)']}, + 'bridge-stp' : + {'help': 'bridge-stp yes/no', + 'example' : ['bridge-stp no'], + 'validvals' : ['yes', 'on', 'off', 'no'], + 'default' : 'no'}, + 'bridge-bridgeprio' : + {'help': 'bridge priority', + 'example' : ['bridge-bridgeprio 32768'], + 'default' : '32768'}, + 'bridge-ageing' : + {'help': 'bridge ageing', + 'example' : ['bridge-ageing 300'], + 'default' : '300'}, + 'bridge-fd' : + { 'help' : 'bridge forward delay', + 'example' : ['bridge-fd 15'], + 'default' : '15'}, + 'bridge-gcint' : + # XXX: recheck values + { 'help' : 'bridge garbage collection interval in secs', + 'example' : ['bridge-gcint 4'], + 'default' : '4'}, + 'bridge-hello' : + { 'help' : 'bridge set hello time', + 'example' : ['bridge-hello 2'], + 'default' : '2'}, + 'bridge-maxage' : + { 'help' : 'bridge set maxage', + 'example' : ['bridge-maxage 20'], + 'default' : '20'}, + 'bridge-pathcosts' : + { 'help' : 'bridge set port path costs', + 'example' : ['bridge-pathcosts swp1=100 swp2=100'], + 'default' : '100'}, + 'bridge-portprios' : + { 'help' : 'bridge port prios', + 'example' : ['bridge-portprios swp1=32 swp2=32'], + 'default' : '32'}, + 'bridge-mclmc' : + { 'help' : 'set multicast last member count', + 'example' : ['bridge-mclmc 2'], + 'default' : '2'}, + 'bridge-mcrouter' : + { 'help' : 'set multicast router', + 'default' : '1', + 'example' : ['bridge-mcrouter 1']}, + 'bridge-mcsnoop' : + { 'help' : 'set multicast snooping', + 'default' : '1', + 'example' : ['bridge-mcsnoop 1']}, + 'bridge-mcsqc' : + { 'help' : 'set multicast startup query count', + 'default' : '2', + 'example' : ['bridge-mcsqc 2']}, + 'bridge-mcqifaddr' : + { 'help' : 'set multicast query to use ifaddr', + 'default' : '0', + 'example' : ['bridge-mcqifaddr 0']}, + 'bridge-mcquerier' : + { 'help' : 'set multicast querier', + 'default' : '0', + 'example' : ['bridge-mcquerier 0']}, + 'bridge-hashel' : + { 'help' : 'set hash elasticity', + 'default' : '4096', + 'example' : ['bridge-hashel 4096']}, + 'bridge-hashmax' : + { 'help' : 'set hash max', + 'default' : '4096', + 'example' : ['bridge-hashmax 4096']}, + 'bridge-mclmi' : + { 'help' : 'set multicast last member interval (in secs)', + 'default' : '1', + 'example' : ['bridge-mclmi 1']}, + 'bridge-mcmi' : + { 'help' : 'set multicast membership interval (in secs)', + 'default' : '260', + 'example' : ['bridge-mcmi 260']}, + 'bridge-mcqpi' : + { 'help' : 'set multicast querier interval (in secs)', + 'default' : '255', + 'example' : ['bridge-mcqpi 255']}, + 'bridge-mcqi' : + { 'help' : 'set multicast query interval (in secs)', + 'default' : '125', + 'example' : ['bridge-mcqi 125']}, + 'bridge-mcqri' : + { 'help' : 'set multicast query response interval (in secs)', + 'default' : '10', + 'example' : ['bridge-mcqri 10']}, + 'bridge-mcsqi' : + { 'help' : 'set multicast startup query interval (in secs)', + 'default' : '31', + 'example' : ['bridge-mcsqi 31']}, + 'bridge-mcqv4src' : + { 'help' : 'set per VLAN v4 multicast querier source address', + 'compat' : True, + 'example' : ['bridge-mcqv4src 100=172.16.100.1 101=172.16.101.1']}, + 'bridge-portmcrouter' : + { 'help' : 'set port multicast routers', + 'default' : '1', + 'example' : ['under the bridge: bridge-portmcrouter swp1=1 swp2=1', + 'under the port: bridge-portmcrouter 1']}, + 'bridge-portmcfl' : + { 'help' : 'port multicast fast leave.', + 'default' : '0', + 'example' : ['under the bridge: bridge-portmcfl swp1=0 swp2=0', + 'under the port: bridge-portmcfl 0']}, + 'bridge-waitport' : + { 'help' : 'wait for a max of time secs for the' + + ' specified ports to become available,' + + 'if no ports are specified then those' + + ' specified on bridge-ports will be' + + ' used here. Specifying no ports here ' + + 'should not be used if we are using ' + + 'regex or \"all\" on bridge_ports,' + + 'as it wouldnt work.', + 'default' : '0', + 'example' : ['bridge-waitport 4 swp1 swp2']}, + 'bridge-maxwait' : + { 'help' : 'forces to time seconds the maximum time ' + + 'that the Debian bridge setup scripts will ' + + 'wait for the bridge ports to get to the ' + + 'forwarding status, doesn\'t allow factional ' + + 'part. If it is equal to 0 then no waiting' + + ' is done', + 'default' : '0', + 'example' : ['bridge-maxwait 3']}, + 'bridge-vids' : + { 'help' : 'bridge port vids. Can be specified ' + + 'under the bridge or under the port. ' + + 'If specified under the bridge the ports ' + + 'inherit it unless overridden by a ' + + 'bridge-vids attribuet under the port', + 'example' : ['bridge-vids 4000', + 'bridge-vids 2000 2200-3000']}, + 'bridge-pvid' : + { 'help' : 'bridge port pvid. Must be specified under' + + ' the bridge port', + 'example' : ['bridge-pvid 1']}, + 'bridge-access' : + { 'help' : 'bridge port access vlan. Must be ' + + 'specified under the bridge port', + 'example' : ['bridge-access 300']}, + 'bridge-port-vids' : + { 'help' : 'bridge vlans', + 'compat': True, + 'example' : ['bridge-port-vids bond0=1-1000,1010-1020']}, + 'bridge-port-pvids' : + { 'help' : 'bridge port vlans', + 'compat': True, + 'example' : ['bridge-port-pvids bond0=100 bond1=200']}, + }} + + # declare some ifaceobj priv_flags. + # XXX: This assumes that the priv_flags is owned by this module + # which it is not. + _BRIDGE_PORT_PROCESSED = 0x1 + + def __init__(self, *args, **kargs): + moduleBase.__init__(self, *args, **kargs) + self.ipcmd = None + self.brctlcmd = None + self._running_vidinfo = {} + self._running_vidinfo_valid = False + self._resv_vlan_range = self._get_reserved_vlan_range() + self.logger.debug('%s: using reserved vlan range %s' + %(self.__class__.__name__, str(self._resv_vlan_range))) + + def _is_bridge(self, ifaceobj): + if ifaceobj.get_attr_value_first('bridge-ports'): + return True + return False + + def _is_bridge_port(self, ifaceobj): + if self.brctlcmd.is_bridge_port(ifaceobj.name): + return True + return False + + def get_dependent_ifacenames(self, ifaceobj, ifacenames_all=None): + if not self._is_bridge(ifaceobj): + return None + if ifaceobj.link_type != ifaceLinkType.LINK_NA: + ifaceobj.link_type = ifaceLinkType.LINK_MASTER + ifaceobj.link_kind = ifaceLinkKind.BRIDGE + return self.parse_port_list(ifaceobj.get_attr_value_first( + 'bridge-ports'), ifacenames_all) + + def get_dependent_ifacenames_running(self, ifaceobj): + self._init_command_handlers() + if not self.brctlcmd.bridge_exists(ifaceobj.name): + return None + return self.brctlcmd.get_bridge_ports(ifaceobj.name) + + def _get_bridge_port_list(self, ifaceobj): + + # port list is also available in the previously + # parsed dependent list. Use that if available, instead + # of parsing port expr again + port_list = ifaceobj.lowerifaces + if port_list: + return port_list + ports = ifaceobj.get_attr_value_first('bridge-ports') + if ports: + return self.parse_port_list(ports) + else: + return None + + def _process_bridge_waitport(self, ifaceobj, portlist): + waitport_value = ifaceobj.get_attr_value_first('bridge-waitport') + if not waitport_value: return + try: + waitportvals = re.split(r'[\s\t]\s*', waitport_value, 1) + if not waitportvals: return + try: + waitporttime = int(waitportvals[0]) + except: + self.log_warn('%s: invalid waitport value \'%s\'' + %(ifaceobj.name, waitporttime)) + return + if waitporttime <= 0: return + try: + waitportlist = self.parse_port_list(waitportvals[1]) + except IndexError, e: + # ignore error and use all bridge ports + waitportlist = portlist + pass + if not waitportlist: return + self.logger.info('%s: waiting for ports %s to exist ...' + %(ifaceobj.name, str(waitportlist))) + starttime = time.time() + while ((time.time() - starttime) < waitporttime): + if all([False for p in waitportlist + if not self.ipcmd.link_exists(p)]): + break; + time.sleep(1) + except Exception, e: + self.log_warn('%s: unable to process waitport: %s' + %(ifaceobj.name, str(e))) + + def _ports_enable_disable_ipv6(self, ports, enable='1'): + for p in ports: + try: + self.write_file('/proc/sys/net/ipv6/conf/%s' %p + + '/disable_ipv6', enable) + except Exception, e: + self.logger.info(str(e)) + pass + + def _add_ports(self, ifaceobj): + bridgeports = self._get_bridge_port_list(ifaceobj) + runningbridgeports = [] + removedbridgeports = [] + + self.ipcmd.batch_start() + self._process_bridge_waitport(ifaceobj, bridgeports) + self.ipcmd.batch_start() + # Delete active ports not in the new port list + if not self.PERFMODE: + runningbridgeports = self.brctlcmd.get_bridge_ports(ifaceobj.name) + if runningbridgeports: + for bport in runningbridgeports: + if not bridgeports or bport not in bridgeports: + self.ipcmd.link_set(bport, 'nomaster') + removedbridgeports.append(bport) + else: + runningbridgeports = [] + if not bridgeports: + self.ipcmd.batch_commit() + return + err = 0 + for bridgeport in Set(bridgeports).difference(Set(runningbridgeports)): + try: + if not self.DRYRUN and not self.ipcmd.link_exists(bridgeport): + self.log_warn('%s: bridge port %s does not exist' + %(ifaceobj.name, bridgeport)) + err += 1 + continue + hwaddress = self.ipcmd.link_get_hwaddress(bridgeport) + if not self._valid_ethaddr(hwaddress): + self.log_warn('%s: skipping port %s, ' %(ifaceobj.name, + bridgeport) + 'invalid ether addr %s' + %hwaddress) + continue + self.ipcmd.link_set(bridgeport, 'master', ifaceobj.name) + self.ipcmd.addr_flush(bridgeport) + except Exception, e: + self.logger.error(str(e)) + pass + try: + self.ipcmd.batch_commit() + except Exception, e: + self.logger.error(str(e)) + pass + + # enable ipv6 for ports that were removed + self._ports_enable_disable_ipv6(removedbridgeports, '0') + if err: + self.log_error('bridge configuration failed (missing ports)') + + + def _process_bridge_maxwait(self, ifaceobj, portlist): + maxwait = ifaceobj.get_attr_value_first('bridge-maxwait') + if not maxwait: return + try: + maxwait = int(maxwait) + except: + self.log_warn('%s: invalid maxwait value \'%s\'' %(ifaceobj.name, + maxwait)) + return + if not maxwait: return + self.logger.info('%s: waiting for ports to go to fowarding state ..' + %ifaceobj.name) + try: + starttime = time.time() + while ((time.time() - starttime) < maxwait): + if all([False for p in portlist + if self.read_file_oneline( + '/sys/class/net/%s/brif/%s/state' + %(ifaceobj.name, p)) != '3']): + break; + time.sleep(1) + except Exception, e: + self.log_warn('%s: unable to process maxwait: %s' + %(ifaceobj.name, str(e))) + + def _ints_to_ranges(self, ints): + for a, b in itertools.groupby(enumerate(ints), lambda (x, y): y - x): + b = list(b) + yield b[0][1], b[-1][1] + + def _ranges_to_ints(self, rangelist): + """ returns expanded list of integers given set of string ranges + example: ['1', '2-4', '6'] returns [1, 2, 3, 4, 6] + """ + result = [] + for part in rangelist: + if '-' in part: + a, b = part.split('-') + a, b = int(a), int(b) + result.extend(range(a, b + 1)) + else: + a = int(part) + result.append(a) + return result + + def _diff_vids(self, vids1, vids2): + vids_to_add = None + vids_to_del = None + + vids1_ints = self._ranges_to_ints(vids1) + vids2_ints = self._ranges_to_ints(vids2) + vids1_diff = Set(vids1_ints).difference(vids2_ints) + vids2_diff = Set(vids2_ints).difference(vids1_ints) + if vids1_diff: + vids_to_add = ['%d' %start if start == end else '%d-%d' %(start, end) + for start, end in self._ints_to_ranges(vids1_diff)] + if vids2_diff: + vids_to_del = ['%d' %start if start == end else '%d-%d' %(start, end) + for start, end in self._ints_to_ranges(vids2_diff)] + return (vids_to_del, vids_to_add) + + def _compare_vids(self, vids1, vids2): + """ Returns true if the vids are same else return false """ + + vids1_ints = self._ranges_to_ints(vids1) + vids2_ints = self._ranges_to_ints(vids2) + if Set(vids1_ints).symmetric_difference(vids2_ints): + return False + else: + return True + + def _set_bridge_mcqv4src_compat(self, ifaceobj): + # + # Sets old style igmp querier + # + attrval = ifaceobj.get_attr_value_first('bridge-mcqv4src') + if attrval: + running_mcqv4src = {} + if not self.PERFMODE: + running_mcqv4src = self.brctlcmd.get_mcqv4src(ifaceobj.name) + mcqs = {} + srclist = attrval.split() + for s in srclist: + k, v = s.split('=') + mcqs[k] = v + + k_to_del = Set(running_mcqv4src.keys()).difference(mcqs.keys()) + for v in k_to_del: + self.brctlcmd.del_mcqv4src(ifaceobj.name, v) + for v in mcqs.keys(): + self.brctlcmd.set_mcqv4src(ifaceobj.name, v, mcqs[v]) + + def _get_running_vidinfo(self): + if self._running_vidinfo_valid: + return self._running_vidinfo + self._running_vidinfo = {} + if not self.PERFMODE: + self._running_vidinfo = self.ipcmd.bridge_port_vids_get_all() + self._running_vidinfo_valid = True + return self._running_vidinfo + + def _flush_running_vidinfo(self): + self._running_vidinfo = {} + self._running_vidinfo_valid = False + + def _set_bridge_vidinfo_compat(self, ifaceobj): + # + # Supports old style vlan vid info format + # for compatibility + # + + # Handle bridge vlan attrs + running_vidinfo = self._get_running_vidinfo() + + # Install pvids + attrval = ifaceobj.get_attr_value_first('bridge-port-pvids') + if attrval: + portlist = self.parse_port_list(attrval) + if not portlist: + self.log_warn('%s: could not parse \'%s %s\'' + %(ifaceobj.name, attrname, attrval)) + return + for p in portlist: + try: + (port, pvid) = p.split('=') + running_pvid = running_vidinfo.get(port, {}).get('pvid') + if running_pvid: + if running_pvid == pvid: + continue + else: + self.ipcmd.bridge_port_pvid_del(port, running_pvid) + self.ipcmd.bridge_port_pvid_add(port, pvid) + except Exception, e: + self.log_warn('%s: failed to set pvid `%s` (%s)' + %(ifaceobj.name, p, str(e))) + + # install port vids + attrval = ifaceobj.get_attr_value_first('bridge-port-vids') + if attrval: + portlist = self.parse_port_list(attrval) + if not portlist: + self.log_warn('%s: could not parse \'%s %s\'' + %(ifaceobj.name, attrname, attrval)) + return + for p in portlist: + try: + (port, val) = p.split('=') + vids = val.split(',') + if running_vidinfo.get(port): + (vids_to_del, vids_to_add) = \ + self._diff_vids(vids, + running_vidinfo.get(port).get('vlan')) + if vids_to_del: + self.ipcmd.bridge_port_vids_del(port, vids_to_del) + if vids_to_add: + self.ipcmd.bridge_port_vids_add(port, vids_to_add) + else: + self.ipcmd.bridge_port_vids_add(port, vids) + except Exception, e: + self.log_warn('%s: failed to set vid `%s` (%s)' + %(ifaceobj.name, p, str(e))) + + # install vids + # XXX: Commenting out this code for now because it was decided + # that this is not needed + #attrval = ifaceobj.get_attr_value_first('bridge-vids') + #if attrval: + # vids = re.split(r'[\s\t]\s*', attrval) + # if running_vidinfo.get(ifaceobj.name): + # (vids_to_del, vids_to_add) = \ + # self._diff_vids(vids, + # running_vidinfo.get(ifaceobj.name).get('vlan')) + # if vids_to_del: + # self.ipcmd.bridge_vids_del(ifaceobj.name, vids_to_del) + # if vids_to_add: + # self.ipcmd.bridge_vids_add(ifaceobj.name, vids_to_add) + # else: + # self.ipcmd.bridge_vids_add(ifaceobj.name, vids) + #else: + # running_vids = running_vidinfo.get(ifaceobj.name) + # if running_vids: + # self.ipcmd.bridge_vids_del(ifaceobj.name, running_vids) + + def _apply_bridge_settings(self, ifaceobj): + try: + stp = ifaceobj.get_attr_value_first('bridge-stp') + if stp: + self.brctlcmd.set_stp(ifaceobj.name, stp) + else: + # If stp not specified and running stp state on, set it to off + running_stp_state = self.read_file_oneline( + '/sys/class/net/%s/bridge/stp_state' %ifaceobj.name) + if running_stp_state and running_stp_state != '0': + self.brctlcmd.set_stp(ifaceobj.name, 'no') + + if ifaceobj.get_attr_value_first('bridge-vlan-aware') == 'yes': + self.write_file('/sys/class/net/%s/bridge/vlan_filtering' + %ifaceobj.name, '1') + # Use the brctlcmd bulk set method: first build a dictionary + # and then call set + bridgeattrs = { k:v for k,v in + {'ageing' : + ifaceobj.get_attr_value_first('bridge-ageing'), + 'bridgeprio' : + ifaceobj.get_attr_value_first( + 'bridge-bridgeprio'), + 'fd' : + ifaceobj.get_attr_value_first('bridge-fd'), + 'gcint' : + ifaceobj.get_attr_value_first('bridge-gcint'), + 'hello' : + ifaceobj.get_attr_value_first('bridge-hello'), + 'maxage' : + ifaceobj.get_attr_value_first('bridge-maxage'), + 'mclmc' : + ifaceobj.get_attr_value_first('bridge-mclmc'), + 'mcrouter' : + ifaceobj.get_attr_value_first( + 'bridge-mcrouter'), + 'mcsnoop' : + ifaceobj.get_attr_value_first('bridge-mcsnoop'), + 'mcsqc' : + ifaceobj.get_attr_value_first('bridge-mcsqc'), + 'mcqifaddr' : + ifaceobj.get_attr_value_first( + 'bridge-mcqifaddr'), + 'mcquerier' : + ifaceobj.get_attr_value_first( + 'bridge-mcquerier'), + 'hashel' : + ifaceobj.get_attr_value_first('bridge-hashel'), + 'hashmax' : + ifaceobj.get_attr_value_first('bridge-hashmax'), + 'mclmi' : + ifaceobj.get_attr_value_first('bridge-mclmi'), + 'mcmi' : + ifaceobj.get_attr_value_first('bridge-mcmi'), + 'mcqpi' : + ifaceobj.get_attr_value_first('bridge-mcqpi'), + 'mcqi' : + ifaceobj.get_attr_value_first('bridge-mcqi'), + 'mcqri' : + ifaceobj.get_attr_value_first('bridge-mcqri'), + 'mcsqi' : + ifaceobj.get_attr_value_first('bridge-mcsqi') + }.items() + if v } + if bridgeattrs: + self.brctlcmd.set_bridge_attrs(ifaceobj.name, bridgeattrs) + portattrs = {} + for attrname, dstattrname in {'bridge-pathcosts' : 'pathcost', + 'bridge-portprios' : 'portprio', + 'bridge-portmcrouter' : 'portmcrouter', + 'bridge-portmcfl' : 'portmcfl'}.items(): + attrval = ifaceobj.get_attr_value_first(attrname) + if not attrval: + continue + portlist = self.parse_port_list(attrval) + if not portlist: + self.log_warn('%s: could not parse \'%s %s\'' + %(ifaceobj.name, attrname, attrval)) + continue + for p in portlist: + try: + (port, val) = p.split('=') + if not portattrs.get(port): + portattrs[port] = {} + portattrs[port].update({dstattrname : val}) + except Exception, e: + self.log_warn('%s: could not parse %s (%s)' + %(ifaceobj.name, attrname, str(e))) + for port, attrdict in portattrs.iteritems(): + try: + self.brctlcmd.set_bridgeport_attrs(ifaceobj.name, port, + attrdict) + except Exception, e: + self.log_warn('%s: %s', str(e)) + pass + self._set_bridge_vidinfo_compat(ifaceobj) + self._set_bridge_mcqv4src_compat(ifaceobj) + self._process_bridge_maxwait(ifaceobj, + self._get_bridge_port_list(ifaceobj)) + except Exception, e: + self.log_warn(str(e)) + + def _check_vids(self, ifaceobj, vids): + ret = True + for v in vids: + if '-' in v: + va, vb = v.split('-') + va, vb = int(va), int(vb) + if (self._handle_reserved_vlan(va, ifaceobj.name) or + self._handle_reserved_vlan(vb, ifaceobj.name)): + ret = False + else: + va = int(v) + if self._handle_reserved_vlan(va, ifaceobj.name): + ret = False + return ret + + def _apply_bridge_vids(self, bportifaceobj, vids, running_vids, isbridge): + try: + if not self._check_vids(bportifaceobj, vids): + return + if running_vids: + (vids_to_del, vids_to_add) = \ + self._diff_vids(vids, running_vids) + if vids_to_del: + self.ipcmd.bridge_vids_del(bportifaceobj.name, + vids_to_del, isbridge) + if vids_to_add: + self.ipcmd.bridge_vids_add(bportifaceobj.name, + vids_to_add, isbridge) + else: + self.ipcmd.bridge_vids_add(bportifaceobj.name, vids, isbridge) + except Exception, e: + self.log_warn('%s: failed to set vid `%s` (%s)' + %(bportifaceobj.name, str(vids), str(e))) + + def _apply_bridge_port_pvids(self, bportifaceobj, pvid, running_pvid): + # Install pvids + try: + if running_pvid: + if running_pvid != pvid: + self.ipcmd.bridge_port_pvid_del(bportifaceobj.name, + running_pvid) + self.ipcmd.bridge_port_pvid_add(bportifaceobj.name, pvid) + else: + self.ipcmd.bridge_port_pvid_add(bportifaceobj.name, pvid) + except Exception, e: + self.log_warn('%s: failed to set pvid `%s` (%s)' + %(bportifaceobj.name, pvid, str(e))) + + def _apply_bridge_vlan_aware_port_settings_all(self, bportifaceobj, + bridge_vids=None, + bridge_pvid=None): + running_vidinfo = self._get_running_vidinfo() + vids = None + pvids = None + bport_access = bportifaceobj.get_attr_value_first('bridge-access') + if bport_access: + vids = re.split(r'[\s\t]\s*', bport_access) + pvids = vids + else: + bport_vids = bportifaceobj.get_attr_value_first('bridge-vids') + if bport_vids: + vids = re.split(r'[\s\t,]\s*', bport_vids) + + bport_pvids = bportifaceobj.get_attr_value_first('bridge-pvid') + if bport_pvids: + pvids = re.split(r'[\s\t]\s*', bport_pvids) + + if pvids: + self._apply_bridge_port_pvids(bportifaceobj, pvids[0], + running_vidinfo.get(bportifaceobj.name, {}).get('pvid')) + elif bridge_pvid: + self._apply_bridge_port_pvids(bportifaceobj, + bridge_pvid, running_vidinfo.get(bportifaceobj.name, + {}).get('pvid')) + # XXX: default pvid is already one + #else: + # self._apply_bridge_port_pvids(bportifaceobj, + # '1', running_vidinfo.get(bportifaceobj.name, + # {}).get('pvid')) + + if vids: + self._apply_bridge_vids(bportifaceobj, vids, + running_vidinfo.get(bportifaceobj.name, + {}).get('vlan'), False) + elif bridge_vids: + self._apply_bridge_vids(bportifaceobj, + bridge_vids, running_vidinfo.get( + bportifaceobj.name, {}).get('vlan'), False) + + + def _apply_bridge_port_settings(self, bportifaceobj, bridgename=None, + bridgeifaceobj=None): + if not bridgename and bridgeifaceobj: + bridgename = bridgeifaceobj.name + # Set other stp and igmp attributes + portattrs = {} + for attrname, dstattrname in { + 'bridge-pathcosts' : 'pathcost', + 'bridge-portprios' : 'portprio', + 'bridge-portmcrouter' : 'portmcrouter', + 'bridge-portmcfl' : 'portmcfl'}.items(): + attrval = bportifaceobj.get_attr_value_first(attrname) + if not attrval: + # Check if bridge has that attribute + #if bridgeifaceobj: + # attrval = bridgeifaceobj.get_attr_value_first(attrname) + # if not attrval: + # continue + #else: + continue + portattrs[dstattrname] = attrval + try: + self.brctlcmd.set_bridgeport_attrs(bridgename, + bportifaceobj.name, portattrs) + except Exception, e: + self.log_warn(str(e)) + + def _apply_bridge_port_settings_all(self, ifaceobj, + ifaceobj_getfunc=None): + err = False + bridge_vlan_aware = ifaceobj.get_attr_value_first( + 'bridge-vlan-aware') + if bridge_vlan_aware and bridge_vlan_aware == 'yes': + bridge_vlan_aware = True + else: + bridge_vlan_aware = False + + if (ifaceobj.get_attr_value_first('bridge-port-vids') and + ifaceobj.get_attr_value_first('bridge-port-pvids')): + # Old style bridge port vid info + # skip new style setting on ports + return + self.logger.info('%s: applying bridge configuration ' + %ifaceobj.name + 'specific to ports') + + bridge_vids = ifaceobj.get_attr_value_first('bridge-vids') + if bridge_vids: + bridge_vids = re.split(r'[\s\t,]\s*', bridge_vids) + else: + bridge_vids = None + + bridge_pvid = ifaceobj.get_attr_value_first('bridge-pvid') + if bridge_pvid: + bridge_pvid = re.split(r'[\s\t]\s*', bridge_pvid)[0] + else: + bridge_pvid = None + + bridgeports = self._get_bridge_port_list(ifaceobj) + if not bridgeports: + self.logger.debug('%s: cannot find bridgeports' %ifaceobj.name) + return + for bport in bridgeports: + # Use the brctlcmd bulk set method: first build a dictionary + # and then call set + if not self.ipcmd.bridge_port_exists(ifaceobj.name, bport): + self.logger.info('%s: skipping bridge config' %ifaceobj.name + + ' for port %s (missing port)' %bport) + continue + self.logger.info('%s: processing bridge config for port %s' + %(ifaceobj.name, bport)) + bportifaceobjlist = ifaceobj_getfunc(bport) + if not bportifaceobjlist: + continue + for bportifaceobj in bportifaceobjlist: + # Dont process bridge port if it already has been processed + if bportifaceobj.priv_flags & self._BRIDGE_PORT_PROCESSED: + continue + try: + # Add attributes specific to the vlan aware bridge + if bridge_vlan_aware: + self._apply_bridge_vlan_aware_port_settings_all( + bportifaceobj, bridge_vids, bridge_pvid) + self._apply_bridge_port_settings(bportifaceobj, + bridgeifaceobj=ifaceobj) + except Exception, e: + err = True + self.logger.warn('%s: %s' %(ifaceobj.name, str(e))) + pass + if err: + raise Exception('%s: errors applying port settings' %ifaceobj.name) + + def _up(self, ifaceobj, ifaceobj_getfunc=None): + # Check if bridge port + bridgename = self.ipcmd.bridge_port_get_bridge_name(ifaceobj.name) + if bridgename: + if self.ipcmd.bridge_is_vlan_aware(bridgename): + bridge_vids = self._get_bridge_vids(bridgename, + ifaceobj_getfunc) + bridge_pvid = self._get_bridge_pvid(bridgename, + ifaceobj_getfunc) + self._apply_bridge_vlan_aware_port_settings_all(ifaceobj, + bridge_vids, + bridge_pvid) + self._apply_bridge_port_settings(ifaceobj, bridgename=bridgename) + ifaceobj.priv_flags |= self._BRIDGE_PORT_PROCESSED + return + if not self._is_bridge(ifaceobj): + return + err = False + errstr = '' + running_ports = '' + try: + if not self.PERFMODE: + if not self.ipcmd.link_exists(ifaceobj.name): + self.ipcmd.link_create(ifaceobj.name, 'bridge') + else: + self.ipcmd.link_create(ifaceobj.name, 'bridge') + except Exception, e: + raise Exception(str(e)) + try: + self._add_ports(ifaceobj) + except Exception, e: + err = True + errstr = str(e) + pass + + try: + self._apply_bridge_settings(ifaceobj) + except Exception, e: + err = True + errstr = str(e) + pass + + try: + running_ports = self.brctlcmd.get_bridge_ports(ifaceobj.name) + if not running_ports: + return + # disable ipv6 for ports that were added to bridge + self._ports_enable_disable_ipv6(running_ports, '1') + self._apply_bridge_port_settings_all(ifaceobj, + ifaceobj_getfunc=ifaceobj_getfunc) + except Exception, e: + err = True + errstr = str(e) + pass + #self._flush_running_vidinfo() + finally: + if ifaceobj.link_type != ifaceLinkType.LINK_NA: + for p in running_ports: + try: + rtnetlink_api.rtnl_api.link_set(p, "up") + except Exception, e: + self.logger.debug('%s: %s: link set up (%s)' + %(ifaceobj.name, p, str(e))) + pass + + if ifaceobj.addr_method == 'manual': + rtnetlink_api.rtnl_api.link_set(ifaceobj.name, "up") + if err: + raise Exception(errstr) + + def _down(self, ifaceobj, ifaceobj_getfunc=None): + try: + if ifaceobj.get_attr_value_first('bridge-ports'): + ports = self.brctlcmd.get_bridge_ports(ifaceobj.name) + self.brctlcmd.delete_bridge(ifaceobj.name) + if ports: + self._ports_enable_disable_ipv6(ports, '0') + if ifaceobj.link_type != ifaceLinkType.LINK_NA: + map(lambda p: rtnetlink_api.rtnl_api.link_set(p, + "down"), ports) + except Exception, e: + self.log_error(str(e)) + + def _query_running_vidinfo_compat(self, ifaceobjrunning, ports): + running_attrs = {} + running_vidinfo = self._get_running_vidinfo() + if ports: + running_bridge_port_vids = '' + for p in ports: + try: + running_vids = running_vidinfo.get(p, {}).get('vlan') + if running_vids: + running_bridge_port_vids += ' %s=%s' %(p, + ','.join(running_vids)) + except Exception: + pass + running_attrs['bridge-port-vids'] = running_bridge_port_vids + + running_bridge_port_pvids = '' + for p in ports: + try: + running_pvids = running_vidinfo.get(p, {}).get('pvid') + if running_pvids: + running_bridge_port_pvids += ' %s=%s' %(p, + running_pvids) + except Exception: + pass + running_attrs['bridge-port-pvids'] = running_bridge_port_pvids + + running_bridge_vids = running_vidinfo.get(ifaceobjrunning.name, + {}).get('vlan') + if running_bridge_vids: + running_attrs['bridge-vids'] = ','.join(running_bridge_vids) + return running_attrs + + def _query_running_vidinfo(self, ifaceobjrunning, ifaceobj_getfunc, + bridgeports=None): + running_attrs = {} + running_vidinfo = self._get_running_vidinfo() + if not running_vidinfo: + return running_attrs + + # 'bridge-vids' under the bridge is all about 'vids' on the port. + # so query the ports + running_bridgeport_vids = [] + running_bridgeport_pvids = [] + for bport in bridgeports: + vids = running_vidinfo.get(bport, {}).get('vlan') + if vids: + running_bridgeport_vids.append(' '.join(vids)) + pvids = running_vidinfo.get(bport, {}).get('pvid') + if pvids: + running_bridgeport_pvids.append(pvids[0]) + + bridge_vids = None + if running_bridgeport_vids: + (vidval, freq) = Counter(running_bridgeport_vids).most_common()[0] + if freq == len(bridgeports): + running_attrs['bridge-vids'] = vidval + bridge_vids = vidval.split() + + bridge_pvid = None + if running_bridgeport_pvids: + (vidval, freq) = Counter(running_bridgeport_pvids).most_common()[0] + if freq == len(bridgeports) and vidval != '1': + running_attrs['bridge-pvid'] = vidval + bridge_pvid = vidval.split() + + # Go through all bridge ports and find their vids + for bport in bridgeports: + bportifaceobj = ifaceobj_getfunc(bport) + if not bportifaceobj: + continue + bport_vids = None + bport_pvids = None + vids = running_vidinfo.get(bport, {}).get('vlan') + if vids and vids != bridge_vids: + bport_vids = vids + pvids = running_vidinfo.get(bport, {}).get('pvid') + if pvids and pvids[0] != bridge_pvid: + bport_pvids = pvids + if not bport_vids and bport_pvids and bport_pvids[0] != '1': + bportifaceobj[0].replace_config('bridge-access', bport_pvids[0]) + else: + if bport_pvids and bport_pvids[0] != '1': + bportifaceobj[0].replace_config('bridge-pvid', bport_pvids[0]) + else: + # delete any stale bridge-vids under ports + bportifaceobj[0].delete_config('bridge-pvid') + if bport_vids: + bportifaceobj[0].replace_config('bridge-vids', + ' '.join(bport_vids)) + else: + # delete any stale bridge-vids under ports + bportifaceobj[0].delete_config('bridge-vids') + return running_attrs + + def _query_running_mcqv4src(self, ifaceobjrunning): + running_mcqv4src = self.brctlcmd.get_mcqv4src(ifaceobjrunning.name) + mcqs = ['%s=%s' %(v, i) for v, i in running_mcqv4src.items()] + mcqs.sort() + mcq = ' '.join(mcqs) + return mcq + + def _query_running_attrs(self, ifaceobjrunning, ifaceobj_getfunc, + bridge_vlan_aware=False): + bridgeattrdict = {} + userspace_stp = 0 + ports = None + skip_kernel_stp_attrs = 0 + + if self.sysctl_get('net.bridge.bridge-stp-user-space') == '1': + userspace_stp = 1 + + tmpbridgeattrdict = self.brctlcmd.get_bridge_attrs(ifaceobjrunning.name) + if not tmpbridgeattrdict: + self.logger.warn('%s: unable to get bridge attrs' + %ifaceobjrunning.name) + return bridgeattrdict + + # Fill bridge_ports and bridge stp attributes first + ports = tmpbridgeattrdict.get('ports') + if ports: + bridgeattrdict['bridge-ports'] = [' '.join(ports.keys())] + stp = tmpbridgeattrdict.get('stp', 'no') + if stp != self.get_mod_subattr('bridge-stp', 'default'): + bridgeattrdict['bridge-stp'] = [stp] + + if stp == 'yes' and userspace_stp: + skip_kernel_stp_attrs = 1 + + # pick all other attributes + for k,v in tmpbridgeattrdict.items(): + if not v: + continue + if k == 'ports' or k == 'stp': + continue + + if skip_kernel_stp_attrs and k[:2] != 'mc': + # only include igmp attributes if kernel stp is off + continue + attrname = 'bridge-' + k + if v != self.get_mod_subattr(attrname, 'default'): + bridgeattrdict[attrname] = [v] + + if bridge_vlan_aware: + bridgevidinfo = self._query_running_vidinfo(ifaceobjrunning, + ifaceobj_getfunc, + ports.keys()) + else: + bridgevidinfo = self._query_running_vidinfo_compat(ifaceobjrunning, + ports) + if bridgevidinfo: + bridgeattrdict.update({k : [v] for k, v in bridgevidinfo.items() + if v}) + + mcq = self._query_running_mcqv4src(ifaceobjrunning) + if mcq: + bridgeattrdict['bridge-mcqv4src'] = [mcq] + + if skip_kernel_stp_attrs: + return bridgeattrdict + + if ports: + portconfig = {'bridge-pathcosts' : '', + 'bridge-portprios' : ''} + for p, v in ports.items(): + v = self.brctlcmd.get_pathcost(ifaceobjrunning.name, p) + if v and v != self.get_mod_subattr('bridge-pathcosts', + 'default'): + portconfig['bridge-pathcosts'] += ' %s=%s' %(p, v) + + v = self.brctlcmd.get_portprio(ifaceobjrunning.name, p) + if v and v != self.get_mod_subattr('bridge-portprios', + 'default'): + portconfig['bridge-portprios'] += ' %s=%s' %(p, v) + + bridgeattrdict.update({k : [v] for k, v in portconfig.items() + if v}) + + return bridgeattrdict + + def _query_check_mcqv4src(self, ifaceobj, ifaceobjcurr): + running_mcqs = self._query_running_mcqv4src(ifaceobj) + attrval = ifaceobj.get_attr_value_first('bridge-mcqv4src') + if attrval: + mcqs = attrval.split() + mcqs.sort() + mcqsout = ' '.join(mcqs) + ifaceobjcurr.update_config_with_status('bridge-mcqv4src', + running_mcqs, 1 if running_mcqs != mcqsout else 0) + + def _query_check_bridge_vidinfo(self, ifaceobj, ifaceobjcurr): + err = 0 + running_vidinfo = self._get_running_vidinfo() + attrval = ifaceobj.get_attr_value_first('bridge-port-vids') + if attrval: + running_bridge_port_vids = '' + portlist = self.parse_port_list(attrval) + if not portlist: + self.log_warn('%s: could not parse \'%s %s\'' + %(ifaceobj.name, attrname, attrval)) + return + err = 0 + for p in portlist: + try: + (port, val) = p.split('=') + vids = val.split(',') + running_vids = running_vidinfo.get(port, {}).get('vlan') + if running_vids: + if not self._compare_vids(vids, running_vids): + err += 1 + running_bridge_port_vids += ' %s=%s' %(port, + ','.join(running_vids)) + else: + running_bridge_port_vids += ' %s' %p + else: + err += 1 + except Exception, e: + self.log_warn('%s: failure checking vid %s (%s)' + %(ifaceobj.name, p, str(e))) + if err: + ifaceobjcurr.update_config_with_status('bridge-port-vids', + running_bridge_port_vids, 1) + else: + ifaceobjcurr.update_config_with_status('bridge-port-vids', + attrval, 0) + + attrval = ifaceobj.get_attr_value_first('bridge-port-pvids') + if attrval: + portlist = self.parse_port_list(attrval) + if not portlist: + self.log_warn('%s: could not parse \'%s %s\'' + %(ifaceobj.name, attrname, attrval)) + return + running_bridge_port_pvids = '' + err = 0 + for p in portlist: + try: + (port, pvid) = p.split('=') + running_pvid = running_vidinfo.get(port, {}).get('pvid') + if running_pvid and running_pvid == pvid: + running_bridge_port_pvids += ' %s' %p + else: + err += 1 + running_bridge_port_pvids += ' %s=%s' %(port, + running_pvid) + except Exception, e: + self.log_warn('%s: failure checking pvid %s (%s)' + %(ifaceobj.name, pvid, str(e))) + if err: + ifaceobjcurr.update_config_with_status('bridge-port-pvids', + running_bridge_port_pvids, 1) + else: + ifaceobjcurr.update_config_with_status('bridge-port-pvids', + running_bridge_port_pvids, 0) + + # XXX: No need to check for bridge-vids on the bridge + # This is used by the ports. The vids on the bridge + # come from the vlan interfaces on the bridge. + # + attrval = ifaceobj.get_attr_value_first('bridge-vids') + #if attrval: + # vids = re.split(r'[\s\t]\s*', attrval) + # running_vids = running_vidinfo.get(ifaceobj.name, {}).get('vlan') + # if running_vids: + # if self._compare_vids(vids, running_vids): + # ifaceobjcurr.update_config_with_status('bridge-vids', + # attrval, 0) + # else: + # ifaceobjcurr.update_config_with_status('bridge-vids', + # ','.join(running_vids), 1) + # else: + # ifaceobjcurr.update_config_with_status('bridge-vids', attrval, + # 1) + if attrval: + ifaceobjcurr.update_config_with_status('bridge-vids', attrval, -1) + + def _query_check_bridge(self, ifaceobj, ifaceobjcurr, + ifaceobj_getfunc=None): + if not self._is_bridge(ifaceobj): + return + if not self.brctlcmd.bridge_exists(ifaceobj.name): + self.logger.info('%s: bridge: does not exist' %(ifaceobj.name)) + return + + ifaceattrs = self.dict_key_subset(ifaceobj.config, + self.get_mod_attrs()) + if not ifaceattrs: + return + try: + runningattrs = self.brctlcmd.get_bridge_attrs(ifaceobj.name) + if not runningattrs: + self.logger.debug('%s: bridge: unable to get bridge attrs' + %ifaceobj.name) + runningattrs = {} + except Exception, e: + self.logger.warn(str(e)) + runningattrs = {} + filterattrs = ['bridge-vids', 'bridge-port-vids', + 'bridge-port-pvids'] + for k in Set(ifaceattrs).difference(filterattrs): + # get the corresponding ifaceobj attr + v = ifaceobj.get_attr_value_first(k) + if not v: + continue + rv = runningattrs.get(k[7:]) + if k == 'bridge-mcqv4src': + continue + if k == 'bridge-vlan-aware' and v == 'yes': + if self.ipcmd.bridge_is_vlan_aware(ifaceobj.name): + ifaceobjcurr.update_config_with_status('bridge-vlan-aware', + v, 0) + else: + ifaceobjcurr.update_config_with_status('bridge-vlan-aware', + v, 1) + elif k == 'bridge-stp': + # special case stp compare because it may + # contain more than one valid values + stp_on_vals = ['on', 'yes'] + stp_off_vals = ['off'] + if ((v in stp_on_vals and rv in stp_on_vals) or + (v in stp_off_vals and rv in stp_off_vals)): + ifaceobjcurr.update_config_with_status('bridge-stp', + v, 0) + else: + ifaceobjcurr.update_config_with_status('bridge-stp', + v, 1) + elif k == 'bridge-ports': + # special case ports because it can contain regex or glob + running_port_list = rv.keys() if rv else [] + bridge_port_list = self._get_bridge_port_list(ifaceobj) + if not running_port_list and not bridge_port_list: + continue + portliststatus = 1 + if running_port_list and bridge_port_list: + difference = set(running_port_list + ).symmetric_difference(bridge_port_list) + if not difference: + portliststatus = 0 + ifaceobjcurr.update_config_with_status('bridge-ports', + ' '.join(running_port_list) + if running_port_list else '', portliststatus) + elif (k == 'bridge-pathcosts' or + k == 'bridge-portprios' or k == 'bridge-portmcrouter' + or k == 'bridge-portmcfl'): + brctlcmdattrname = k[11:].rstrip('s') + # for port attributes, the attributes are in a list + # = + status = 0 + currstr = '' + vlist = self.parse_port_list(v) + if not vlist: + continue + for vlistitem in vlist: + try: + (p, v) = vlistitem.split('=') + currv = self.brctlcmd.get_bridgeport_attr( + ifaceobj.name, p, + brctlcmdattrname) + if currv: + currstr += ' %s=%s' %(p, currv) + else: + currstr += ' %s=%s' %(p, 'None') + if currv != v: + status = 1 + except Exception, e: + self.log_warn(str(e)) + pass + ifaceobjcurr.update_config_with_status(k, currstr, status) + elif not rv: + ifaceobjcurr.update_config_with_status(k, 'notfound', 1) + continue + elif v != rv: + ifaceobjcurr.update_config_with_status(k, rv, 1) + else: + ifaceobjcurr.update_config_with_status(k, rv, 0) + + self._query_check_bridge_vidinfo(ifaceobj, ifaceobjcurr) + + self._query_check_mcqv4src(ifaceobj, ifaceobjcurr) + + def _get_bridge_vids(self, bridgename, ifaceobj_getfunc): + ifaceobjs = ifaceobj_getfunc(bridgename) + for ifaceobj in ifaceobjs: + vids = ifaceobj.get_attr_value_first('bridge-vids') + if vids: return re.split(r'[\s\t,]\s*', vids) + return None + + def _get_bridge_pvid(self, bridgename, ifaceobj_getfunc): + ifaceobjs = ifaceobj_getfunc(bridgename) + pvid = None + for ifaceobj in ifaceobjs: + pvid = ifaceobj.get_attr_value_first('bridge-pvid') + return pvid + + def _get_bridge_name(self, ifaceobj): + return self.ipcmd.bridge_port_get_bridge_name(ifaceobj.name) + + def _query_check_bridge_port_vidinfo(self, ifaceobj, ifaceobjcurr, + ifaceobj_getfunc, bridgename): + running_vidinfo = self._get_running_vidinfo() + + attr_name = 'bridge-access' + vids = ifaceobj.get_attr_value_first(attr_name) + if vids: + running_pvids = running_vidinfo.get(ifaceobj.name, + {}).get('pvid') + running_vids = running_vidinfo.get(ifaceobj.name, + {}).get('vlan') + if (not running_pvids or running_pvids != vids or + running_vids): + ifaceobjcurr.update_config_with_status(attr_name, + running_pvids, 1) + else: + ifaceobjcurr.update_config_with_status(attr_name, vids, 0) + return + + attr_name = 'bridge-vids' + vids = ifaceobj.get_attr_value_first(attr_name) + if vids: + vids = re.split(r'[\s\t]\s*', vids) + running_vids = running_vidinfo.get(ifaceobj.name, + {}).get('vlan') + if not running_vids or not self._compare_vids(vids, running_vids): + ifaceobjcurr.update_config_with_status(attr_name, + ' '.join(running_vids), 1) + else: + ifaceobjcurr.update_config_with_status(attr_name, + ' '.join(running_vids), 0) + else: + # check if it matches the bridge vids + bridge_vids = self._get_bridge_vids(bridgename, ifaceobj_getfunc) + running_vids = running_vidinfo.get(ifaceobj.name, + {}).get('vlan') + if (bridge_vids and (not running_vids or + not self._compare_vids(bridge_vids, running_vids))): + ifaceobjcurr.status = ifaceStatus.ERROR + ifaceobjcurr.status_str = 'bridge vid error' + + running_pvid = running_vidinfo.get(ifaceobj.name, + {}).get('pvid') + attr_name = 'bridge-pvid' + pvid = ifaceobj.get_attr_value_first(attr_name) + if pvid: + if running_pvid and running_pvid == pvid: + ifaceobjcurr.update_config_with_status(attr_name, + running_pvid, 0) + else: + ifaceobjcurr.update_config_with_status(attr_name, + running_pvid, 1) + elif not running_pvid or running_pvid != '1': + ifaceobjcurr.status = ifaceStatus.ERROR + ifaceobjcurr.status_str = 'bridge pvid error' + + def _query_check_bridge_port(self, ifaceobj, ifaceobjcurr, + ifaceobj_getfunc): + if not self._is_bridge_port(ifaceobj): + # Mark all bridge attributes as failed + ifaceobjcurr.check_n_update_config_with_status_many(ifaceobj, + ['bridge-vids', 'bridge-pvid', 'bridge-access', + 'bridge-pathcosts', 'bridge-portprios', + 'bridge-portmcrouter', + 'bridge-portmcfl'], 1) + return + bridgename = self._get_bridge_name(ifaceobj) + if not bridgename: + self.logger.warn('%s: unable to determine bridge name' + %ifaceobj.name) + return + + if self.ipcmd.bridge_is_vlan_aware(bridgename): + self._query_check_bridge_port_vidinfo(ifaceobj, ifaceobjcurr, + ifaceobj_getfunc, + bridgename) + for attr, dstattr in {'bridge-pathcosts' : 'pathcost', + 'bridge-portprios' : 'priority', + 'bridge-portmcrouter' : 'mcrouter', + 'bridge-portmcfl' : 'mcfl' }.items(): + attrval = ifaceobj.get_attr_value_first(attr) + if not attrval: + continue + + try: + running_attrval = self.brctlcmd.get_bridgeport_attr( + bridgename, ifaceobj.name, dstattr) + if running_attrval != attrval: + ifaceobjcurr.update_config_with_status(attr, + running_attrval, 1) + else: + ifaceobjcurr.update_config_with_status(attr, + running_attrval, 0) + except Exception, e: + self.log_warn('%s: %s' %(ifaceobj.name, str(e))) + + def _query_check(self, ifaceobj, ifaceobjcurr, ifaceobj_getfunc=None): + if self._is_bridge(ifaceobj): + self._query_check_bridge(ifaceobj, ifaceobjcurr) + else: + self._query_check_bridge_port(ifaceobj, ifaceobjcurr, + ifaceobj_getfunc) + + def _query_running_bridge(self, ifaceobjrunning, ifaceobj_getfunc): + if self.ipcmd.bridge_is_vlan_aware(ifaceobjrunning.name): + ifaceobjrunning.update_config('bridge-vlan-aware', 'yes') + ifaceobjrunning.update_config_dict(self._query_running_attrs( + ifaceobjrunning, + ifaceobj_getfunc, + bridge_vlan_aware=True)) + else: + ifaceobjrunning.update_config_dict(self._query_running_attrs( + ifaceobjrunning, None)) + + def _query_running_bridge_port_attrs(self, ifaceobjrunning, bridgename): + if self.sysctl_get('net.bridge.bridge-stp-user-space') == '1': + return + + v = self.brctlcmd.get_pathcost(bridgename, ifaceobjrunning.name) + if v and v != self.get_mod_subattr('bridge-pathcosts', 'default'): + ifaceobjrunning.update_config('bridge-pathcosts', v) + + v = self.brctlcmd.get_pathcost(bridgename, ifaceobjrunning.name) + if v and v != self.get_mod_subattr('bridge-portprios', 'default'): + ifaceobjrunning.update_config('bridge-portprios', v) + + def _query_running_bridge_port(self, ifaceobjrunning, + ifaceobj_getfunc=None): + bridgename = self.ipcmd.bridge_port_get_bridge_name( + ifaceobjrunning.name) + bridge_vids = None + bridge_pvid = None + if not bridgename: + self.logger.warn('%s: unable to find bridgename' + %ifaceobjrunning.name) + return + if not self.ipcmd.bridge_is_vlan_aware(bridgename): + return + + running_vidinfo = self._get_running_vidinfo() + bridge_port_vids = running_vidinfo.get(ifaceobjrunning.name, + {}).get('vlan') + bridge_port_pvid = running_vidinfo.get(ifaceobjrunning.name, + {}).get('pvid') + + bridgeifaceobjlist = ifaceobj_getfunc(bridgename) + if bridgeifaceobjlist: + bridge_vids = bridgeifaceobjlist[0].get_attr_value('bridge-vids') + bridge_pvid = bridgeifaceobjlist[0].get_attr_value_first('bridge-pvid') + + if not bridge_port_vids and bridge_port_pvid: + # must be an access port + if bridge_port_pvid != '1': + ifaceobjrunning.update_config('bridge-access', + bridge_port_pvid) + else: + if bridge_port_vids: + if (not bridge_vids or bridge_port_vids != bridge_vids): + ifaceobjrunning.update_config('bridge-vids', + ' '.join(bridge_port_vids)) + if bridge_port_pvid and bridge_port_pvid != '1': + if (not bridge_pvid or (bridge_port_pvid != bridge_pvid)): + ifaceobjrunning.update_config('bridge-pvid', + bridge_port_pvid) + self._query_running_bridge_port_attrs(ifaceobjrunning, bridgename) + + def _query_running(self, ifaceobjrunning, ifaceobj_getfunc=None): + if self.brctlcmd.bridge_exists(ifaceobjrunning.name): + self._query_running_bridge(ifaceobjrunning, ifaceobj_getfunc) + elif self.brctlcmd.is_bridge_port(ifaceobjrunning.name): + self._query_running_bridge_port(ifaceobjrunning, ifaceobj_getfunc) + + _run_ops = {'pre-up' : _up, + 'post-down' : _down, + 'query-checkcurr' : _query_check, + 'query-running' : _query_running} + + def get_ops(self): + """ returns list of ops supported by this module """ + return self._run_ops.keys() + + def _init_command_handlers(self): + flags = self.get_flags() + if not self.ipcmd: + self.ipcmd = iproute2(**flags) + if not self.brctlcmd: + self.brctlcmd = brctl(**flags) + + def run(self, ifaceobj, operation, query_ifaceobj=None, + ifaceobj_getfunc=None): + """ run bridge configuration on the interface object passed as + argument. Can create bridge interfaces if they dont exist already + + Args: + **ifaceobj** (object): iface object + + **operation** (str): any of 'pre-up', 'post-down', 'query-checkcurr', + 'query-running' + + Kwargs: + **query_ifaceobj** (object): query check ifaceobject. This is only + valid when op is 'query-checkcurr'. It is an object same as + ifaceobj, but contains running attribute values and its config + status. The modules can use it to return queried running state + of interfaces. status is success if the running state is same + as user required state in ifaceobj. error otherwise. + """ + op_handler = self._run_ops.get(operation) + if not op_handler: + return + self._init_command_handlers() + self._flush_running_vidinfo() + if operation == 'query-checkcurr': + op_handler(self, ifaceobj, query_ifaceobj, + ifaceobj_getfunc=ifaceobj_getfunc) + else: + op_handler(self, ifaceobj, ifaceobj_getfunc=ifaceobj_getfunc) diff --git a/ifupdown2/addons/bridgevlan.py b/ifupdown2/addons/bridgevlan.py new file mode 100644 index 0000000..44824c7 --- /dev/null +++ b/ifupdown2/addons/bridgevlan.py @@ -0,0 +1,170 @@ +#!/usr/bin/python +# +# Copyright 2014 Cumulus Networks, Inc. All rights reserved. +# Author: Roopa Prabhu, roopa@cumulusnetworks.com +# + +from ifupdown.iface import * +from ifupdownaddons.modulebase import moduleBase +from ifupdownaddons.iproute2 import iproute2 +from ifupdownaddons.bridgeutils import brctl +import logging + +class bridgevlan(moduleBase): + """ ifupdown2 addon module to configure vlan attributes on a vlan + aware bridge """ + + _modinfo = {'mhelp' : 'bridgevlan module configures vlan attributes ' + + 'on a vlan aware bridge. This module only ' + + 'understands vlan interface name ' + + 'with dot notations. eg br0.100. where br0 is the ' + + 'vlan aware bridge this config is for', + 'attrs' : { + 'bridge-igmp-querier-src' : + { 'help' : 'bridge igmp querier src. Must be ' + + 'specified under the vlan interface', + 'example' : ['bridge-igmp-querier-src 172.16.101.1']}}} + + def __init__(self, *args, **kargs): + moduleBase.__init__(self, *args, **kargs) + self.brctlcmd = None + self.ipcmd = None + + def _is_bridge_vlan_device(self, ifaceobj): + if ifaceobj.type == ifaceType.BRIDGE_VLAN: + return True + return False + + def _get_bridge_n_vlan(self, ifaceobj): + vlist = ifaceobj.name.split('.', 1) + if len(vlist) == 2: + return (vlist[0], vlist[1]) + return None + + def _get_bridgename(self, ifaceobj): + vlist = ifaceobj.name.split('.', 1) + if len(vlist) == 2: + return vlist[0] + return None + + def get_dependent_ifacenames(self, ifaceobj, ifaceobjs_all=None): + if not self._is_bridge_vlan_device(ifaceobj): + return None + return [self._get_bridgename(ifaceobj)] + + def _up(self, ifaceobj): + try: + (bridgename, vlan) = self._get_bridge_n_vlan(ifaceobj) + vlanid = int(vlan, 10) + except: + self.logger.warn('%s: bridge vlan interface name ' %ifaceobj.name + + 'does not correspond to format (eg. br0.100)') + raise + + if not self.ipcmd.link_exists(bridgename): + #self.logger.warn('%s: bridge %s does not exist' %(ifaceobj.name, + # bridgename)) + return + + running_mcqv4src = {} + if not self.PERFMODE: + running_mcqv4src = self.brctlcmd.get_mcqv4src(bridgename) + if running_mcqv4src: + r_mcqv4src = running_mcqv4src.get(vlan) + else: + r_mcqv4src = None + mcqv4src = ifaceobj.get_attr_value_first('bridge-igmp-querier-src') + if not mcqv4src: + if r_mcqv4src: + self.brctlcmd.del_mcqv4src(bridgename, vlanid) + return + + if r_mcqv4src and r_mcqv4src != mcqv4src: + self.brctlcmd.del_mcqv4src(bridgename, vlanid) + self.brctlcmd.set_mcqv4src(bridgename, vlanid, mcqv4src) + else: + self.brctlcmd.set_mcqv4src(bridgename, vlanid, mcqv4src) + + def _down(self, ifaceobj): + try: + (bridgename, vlan) = self._get_bridge_n_vlan(ifaceobj) + vlanid = int(vlan, 10) + except: + self.logger.warn('%s: bridge vlan interface name ' %ifaceobj.name + + 'does not correspond to format (eg. br0.100)') + raise + + if not self.ipcmd.link_exists(bridgename): + #self.logger.warn('%s: bridge %s does not exist' %(ifaceobj.name, + # bridgename)) + return + mcqv4src = ifaceobj.get_attr_value_first('bridge-igmp-querier-src') + if mcqv4src: + self.brctlcmd.del_mcqv4src(bridgename, vlanid) + + def _query_running_bridge_igmp_querier_src(self, ifaceobj): + (bridgename, vlanid) = ifaceobj.name.split('.') + running_mcqv4src = self.brctlcmd.get_mcqv4src(bridgename) + if running_mcqv4src: + return running_mcqv4src.get(vlanid) + return None + + def _query_check(self, ifaceobj, ifaceobjcurr): + attrval = ifaceobj.get_attr_value_first('bridge-igmp-querier-src') + if attrval: + running_mcq = self._query_running_bridge_igmp_querier_src(ifaceobj) + if not running_mcq or running_mcq != attrval: + ifaceobjcurr.update_config_with_status( + 'bridge-igmp-querier-src', running_mcq, 1) + else: + ifaceobjcurr.update_config_with_status( + 'bridge-igmp-querier-src', attrval, 0) + ifaceobjcurr.status = ifaceStatus.SUCCESS + return + + def _query_running(self, ifaceobjrunning): + # XXX not supported + return + + _run_ops = {'pre-up' : _up, + 'post-down' : _down, + 'query-checkcurr' : _query_check, + 'query-running' : _query_running} + + def get_ops(self): + """ returns list of ops supported by this module """ + return self._run_ops.keys() + + def _init_command_handlers(self): + if not self.ipcmd: + self.ipcmd = iproute2(**self.get_flags()) + if not self.brctlcmd: + self.brctlcmd = brctl(**self.get_flags()) + + def run(self, ifaceobj, operation, query_ifaceobj=None, **extra_args): + """ run vlan configuration on the interface object passed as argument + + Args: + **ifaceobj** (object): iface object + + **operation** (str): any of 'pre-up', 'post-down', 'query-checkcurr', + 'query-running' + Kwargs: + **query_ifaceobj** (object): query check ifaceobject. This is only + valid when op is 'query-checkcurr'. It is an object same as + ifaceobj, but contains running attribute values and its config + status. The modules can use it to return queried running state + of interfaces. status is success if the running state is same + as user required state in ifaceobj. error otherwise. + """ + op_handler = self._run_ops.get(operation) + if not op_handler: + return + if (operation != 'query-running' and + not self._is_bridge_vlan_device(ifaceobj)): + return + self._init_command_handlers() + if operation == 'query-checkcurr': + op_handler(self, ifaceobj, query_ifaceobj) + else: + op_handler(self, ifaceobj) diff --git a/ifupdown2/addons/dhcp.py b/ifupdown2/addons/dhcp.py new file mode 100644 index 0000000..ed68466 --- /dev/null +++ b/ifupdown2/addons/dhcp.py @@ -0,0 +1,129 @@ +#!/usr/bin/python +# +# Copyright 2014 Cumulus Networks, Inc. All rights reserved. +# Author: Roopa Prabhu, roopa@cumulusnetworks.com +# + +try: + from ipaddr import IPNetwork + from sets import Set + from ifupdown.iface import * + from ifupdownaddons.modulebase import moduleBase + from ifupdownaddons.dhclient import dhclient + from ifupdownaddons.iproute2 import iproute2 +except ImportError, e: + raise ImportError (str(e) + "- required module not found") + +class dhcp(moduleBase): + """ ifupdown2 addon module to configure dhcp on interface """ + + def __init__(self, *args, **kargs): + moduleBase.__init__(self, *args, **kargs) + self.dhclientcmd = dhclient(**kargs) + self.ipcmd = None + + def _up(self, ifaceobj): + try: + if ifaceobj.addr_family == 'inet': + # First release any existing dhclient processes + try: + if not self.PERFMODE: + self.dhclientcmd.stop(ifaceobj.name) + except: + pass + self.dhclientcmd.start(ifaceobj.name) + elif ifaceobj.addr_family == 'inet6': + accept_ra = ifaceobj.get_attr_value_first('accept_ra') + if accept_ra: + # XXX: Validate value + self.sysctl_set('net.ipv6.conf.%s' %ifaceobj.name + + '.accept_ra', accept_ra) + autoconf = ifaceobj.get_attr_value_first('autoconf') + if autoconf: + # XXX: Validate value + self.sysctl_set('net.ipv6.conf.%s' %ifaceobj.name + + '.autoconf', autoconf) + try: + self.dhclientcmd.stop6(ifaceobj.name) + except: + pass + self.dhclientcmd.start6(ifaceobj.name) + except Exception, e: + self.log_error(str(e)) + + def _down(self, ifaceobj): + self.dhclientcmd.release(ifaceobj.name) + self.ipcmd.link_down(ifaceobj.name) + + def _query_check(self, ifaceobj, ifaceobjcurr): + if self.dhclientcmd.is_running(ifaceobjcurr.name): + ifaceobjcurr.addr_family = 'inet' + if ifaceobj.addr_family != 'inet': + ifaceobjcurr.status = ifaceStatus.ERROR + ifaceobjcurr.addr_method = 'dhcp' + ifaceobjcurr.status = ifaceStatus.SUCCESS + elif self.dhclientcmd.is_running6(ifaceobjcurr.name): + ifaceobjcurr.addr_family = 'inet6' + if ifaceobj.addr_family != 'inet6': + ifaceobjcurr.status = ifaceStatus.ERROR + ifaceobjcurr.addr_method = 'dhcp' + ifaceobjcurr.status = ifaceStatus.SUCCESS + else: + ifaceobjcurr.addr_family = None + ifaceobjcurr.status = ifaceStatus.ERROR + + def _query_running(self, ifaceobjrunning): + if not self.ipcmd.link_exists(ifaceobjrunning.name): + return + if self.dhclientcmd.is_running(ifaceobjrunning.name): + ifaceobjrunning.addr_family = 'inet' + ifaceobjrunning.addr_method = 'dhcp' + elif self.dhclientcmd.is_running6(ifaceobjrunning.name): + ifaceobjrunning.addr_family = 'inet6' + ifaceobjrunning.addr_method = 'dhcp6' + + _run_ops = {'up' : _up, + 'down' : _down, + 'query-checkcurr' : _query_check, + 'query-running' : _query_running } + + def get_ops(self): + """ returns list of ops supported by this module """ + return self._run_ops.keys() + + def _init_command_handlers(self): + if not self.ipcmd: + self.ipcmd = iproute2(**self.get_flags()) + + def run(self, ifaceobj, operation, query_ifaceobj=None, **extra_args): + """ run dhcp configuration on the interface object passed as argument + + Args: + **ifaceobj** (object): iface object + + **operation** (str): any of 'up', 'down', 'query-checkcurr', + 'query-running' + + Kwargs: + **query_ifaceobj** (object): query check ifaceobject. This is only + valid when op is 'query-checkcurr'. It is an object same as + ifaceobj, but contains running attribute values and its config + status. The modules can use it to return queried running state + of interfaces. status is success if the running state is same + as user required state in ifaceobj. error otherwise. + """ + op_handler = self._run_ops.get(operation) + if not op_handler: + return + try: + if (operation != 'query-running' and + (ifaceobj.addr_method != 'dhcp' and + ifaceobj.addr_method != 'dhcp6')): + return + except: + return + self._init_command_handlers() + if operation == 'query-checkcurr': + op_handler(self, ifaceobj, query_ifaceobj) + else: + op_handler(self, ifaceobj) diff --git a/ifupdown2/addons/ethtool.py b/ifupdown2/addons/ethtool.py new file mode 100644 index 0000000..579fdfa --- /dev/null +++ b/ifupdown2/addons/ethtool.py @@ -0,0 +1,133 @@ +#!/usr/bin/python +# +# Copyright 2014 Cumulus Networks, Inc. All rights reserved. +# Author: Roopa Prabhu, roopa@cumulusnetworks.com +# + +try: + from ipaddr import IPNetwork + from sets import Set + from ifupdown.iface import * + from ifupdownaddons.modulebase import moduleBase + from ifupdownaddons.iproute2 import iproute2 +except ImportError, e: + raise ImportError (str(e) + "- required module not found") + +class ethtool(moduleBase): + """ ifupdown2 addon module to configure ethtool attributes """ + + _modinfo = {'mhelp' : 'ethtool configuration module for interfaces', + 'attrs': { + 'link-speed' : + {'help' : 'set link speed', + 'example' : ['link-speed 1000']}, + 'link-duplex' : + {'help': 'set link duplex', + 'example' : ['link-duplex full'], + 'validvals' : ['half', 'full'], + 'default' : 'half'}, + 'link-autoneg' : + {'help': 'set autonegotiation', + 'example' : ['link-autoneg on'], + 'validvals' : ['on', 'off'], + 'default' : 'off'}}} + + def __init__(self, *args, **kargs): + moduleBase.__init__(self, *args, **kargs) + self.ipcmd = None + + def _post_up(self, ifaceobj): + if not self.ipcmd.link_exists(ifaceobj.name): + return + cmd = '' + attrval = ifaceobj.get_attr_value_first('link-speed') + if attrval: + cmd += ' speed %s' %attrval + attrval = ifaceobj.get_attr_value_first('link-duplex') + if attrval: + cmd += ' duplex %s' %attrval + attrval = ifaceobj.get_attr_value_first('link-autoneg') + if attrval: + cmd += ' autoneg %s' %attrval + if cmd: + try: + cmd = 'ethtool -s %s %s' %(ifaceobj.name, cmd) + self.exec_command(cmd) + except Exception, e: + ifaceobj.status = ifaceStatus.ERROR + self.log_warn('%s: %s' %(ifaceobj.name, str(e))) + + def _query_check(self, ifaceobj, ifaceobjcurr): + """ + Advertised auto-negotiation: No + Speed: 1000Mb/s + Duplex: Full""" + ethtool_attrs = self.dict_key_subset(ifaceobj.config, + self.get_mod_attrs()) + if not ethtool_attrs: + return + try: + speed = ifaceobj.get_attr_value_first('link-speed') + if speed: + running_speed = self.read_file_oneline( + '/sys/class/net/%s/speed' %ifaceobj.name) + if running_speed and running_speed != speed: + ifaceobjcurr.update_config_with_status('link-speed', + running_speed, 1) + else: + ifaceobjcurr.update_config_with_status('link-speed', + running_speed, 0) + duplex = ifaceobj.get_attr_value_first('link-duplex') + if duplex: + running_duplex = self.read_file_oneline( + '/sys/class/net/%s/duplex' %ifaceobj.name) + if running_duplex and running_duplex != duplex: + ifaceobjcurr.update_config_with_status('link-duplex', + running_duplex, 1) + else: + ifaceobjcurr.update_config_with_status('link-duplex', + running_duplex, 0) + except Exception: + pass + return + + def _query_running(self, ifaceobjrunning): + return + + _run_ops = {'post-up' : _post_up, + 'query-checkcurr' : _query_check, + 'query-running' : _query_running } + + def get_ops(self): + """ returns list of ops supported by this module """ + return self._run_ops.keys() + + def _init_command_handlers(self): + if not self.ipcmd: + self.ipcmd = iproute2(**self.get_flags()) + + def run(self, ifaceobj, operation, query_ifaceobj=None, **extra_args): + """ run ethtool configuration on the interface object passed as + argument + + Args: + **ifaceobj** (object): iface object + + **operation** (str): any of 'post-up', 'query-checkcurr', + 'query-running' + Kwargs: + **query_ifaceobj** (object): query check ifaceobject. This is only + valid when op is 'query-checkcurr'. It is an object same as + ifaceobj, but contains running attribute values and its config + status. The modules can use it to return queried running state + of interfaces. status is success if the running state is same + as user required state in ifaceobj. error otherwise. + """ + op_handler = self._run_ops.get(operation) + if not op_handler: + return + self._init_command_handlers() + if operation == 'query-checkcurr': + op_handler(self, ifaceobj, query_ifaceobj) + else: + op_handler(self, ifaceobj) diff --git a/ifupdown2/addons/ifenslave.py b/ifupdown2/addons/ifenslave.py new file mode 100644 index 0000000..6c5bda3 --- /dev/null +++ b/ifupdown2/addons/ifenslave.py @@ -0,0 +1,438 @@ +#!/usr/bin/python +# +# Copyright 2014 Cumulus Networks, Inc. All rights reserved. +# Author: Roopa Prabhu, roopa@cumulusnetworks.com +# + +from sets import Set +from ifupdown.iface import * +import ifupdownaddons +from ifupdownaddons.modulebase import moduleBase +from ifupdownaddons.ifenslaveutil import ifenslaveutil +from ifupdownaddons.iproute2 import iproute2 +import ifupdown.rtnetlink_api as rtnetlink_api + +class ifenslave(moduleBase): + """ ifupdown2 addon module to configure bond interfaces """ + _modinfo = { 'mhelp' : 'bond configuration module', + 'attrs' : { + 'bond-use-carrier': + {'help' : 'bond use carrier', + 'validvals' : ['0', '1'], + 'default' : '1', + 'example': ['bond-use-carrier 1']}, + 'bond-num-grat-arp': + {'help' : 'bond use carrier', + 'validrange' : ['0', '255'], + 'default' : '1', + 'example' : ['bond-num-grat-arp 1']}, + 'bond-num-unsol-na' : + {'help' : 'bond slave devices', + 'validrange' : ['0', '255'], + 'default' : '1', + 'example' : ['bond-num-unsol-na 1']}, + 'bond-xmit-hash-policy' : + {'help' : 'bond slave devices', + 'validvals' : ['layer2', 'layer3+4', 'layer2+3'], + 'default' : 'layer2', + 'example' : ['bond-xmit-hash-policy layer2']}, + 'bond-miimon' : + {'help' : 'bond miimon', + 'validrange' : ['0', '255'], + 'default' : '0', + 'example' : ['bond-miimon 0']}, + 'bond-mode' : + {'help' : 'bond mode', + 'validvals' : ['balance-rr', 'active-backup', + 'balance-xor', 'broadcast', '802.3ad', + 'balance-tlb', 'balance-alb'], + 'default' : 'balance-rr', + 'example' : ['bond-mode 802.3ad']}, + 'bond-lacp-rate': + {'help' : 'bond lacp rate', + 'validvals' : ['0', '1'], + 'default' : '0', + 'example' : ['bond-lacp-rate 0']}, + 'bond-min-links': + {'help' : 'bond min links', + 'default' : '0', + 'example' : ['bond-min-links 0']}, + 'bond-ad-sys-priority': + {'help' : '802.3ad system priority', + 'default' : '65535', + 'example' : ['bond-ad-sys-priority 65535']}, + 'bond-ad-sys-mac-addr': + {'help' : '802.3ad system mac address', + 'default' : '00:00:00:00:00:00', + 'example' : ['bond-ad-sys-mac-addr 00:00:00:00:00:00']}, + 'bond-lacp-fallback-allow': + {'help' : 'allow lacp fall back', + 'compat' : True, + 'validvals' : ['0', '1'], + 'default' : '0', + 'example' : ['bond-lacp-fallback-allow 0']}, + 'bond-lacp-fallback-period': + {'help' : 'grace period (seconds) for lacp fall back', + 'compat' : True, + 'validrange' : ['0', '100'], + 'default' : '90', + 'example' : ['bond-lacp-fallback-period 100']}, + 'bond-lacp-fallback-priority': + {'help' : 'slave priority for lacp fall back', + 'compat' : True, + 'example' : ['bond-lacp-fallback-priority swp1=1 swp2=1 swp3=2']}, + 'bond-lacp-bypass-allow': + {'help' : 'allow lacp bypass', + 'validvals' : ['0', '1'], + 'default' : '0', + 'example' : ['bond-lacp-bypass-allow 0']}, + 'bond-lacp-bypass-period': + {'help' : 'grace period (seconds) for lacp bypass', + 'validrange' : ['0', '900'], + 'default' : '0', + 'example' : ['bond-lacp-bypass-period 100']}, + 'bond-lacp-bypass-priority': + {'help' : 'slave priority for lacp bypass', + 'example' : ['bond-lacp-bypass-priority swp1=1 swp2=1 swp3=2']}, + 'bond-slaves' : + {'help' : 'bond slaves', + 'required' : True, + 'example' : ['bond-slaves swp1 swp2', + 'bond-slaves glob swp1-2', + 'bond-slaves regex (swp[1|2)']}}} + + def __init__(self, *args, **kargs): + ifupdownaddons.modulebase.moduleBase.__init__(self, *args, **kargs) + self.ipcmd = None + self.ifenslavecmd = None + + def _is_bond(self, ifaceobj): + if ifaceobj.get_attr_value_first('bond-slaves'): + return True + return False + + def get_dependent_ifacenames(self, ifaceobj, ifacenames_all=None): + """ Returns list of interfaces dependent on ifaceobj """ + + if not self._is_bond(ifaceobj): + return None + slave_list = self.parse_port_list(ifaceobj.get_attr_value_first( + 'bond-slaves'), ifacenames_all) + + # Also save a copy for future use + ifaceobj.priv_data = list(slave_list) + if ifaceobj.link_type != ifaceLinkType.LINK_NA: + ifaceobj.link_type = ifaceLinkType.LINK_MASTER + return slave_list + + def get_dependent_ifacenames_running(self, ifaceobj): + self._init_command_handlers() + return self.ifenslavecmd.get_slaves(ifaceobj.name) + + def _get_slave_list(self, ifaceobj): + """ Returns slave list present in ifaceobj config """ + + # If priv data already has slave list use that first. + if ifaceobj.priv_data: + return ifaceobj.priv_data + slaves = ifaceobj.get_attr_value_first('bond-slaves') + if slaves: + return self.parse_port_list(slaves) + else: + return None + + def fetch_attr(self, ifaceobj, attrname): + attrval = ifaceobj.get_attr_value_first(attrname) + if attrval: + msg = ('%s: invalid value %s for attr %s.' + %(ifaceobj.name, attrval, attrname)) + optiondict = self.get_mod_attr(attrname) + if not optiondict: + return None + validvals = optiondict.get('validvals') + if validvals and attrval not in validvals: + raise Exception(msg + ' Valid values are %s' %str(validvals)) + validrange = optiondict.get('validrange') + if validrange: + if (int(attrval) < int(validrange[0]) or + int(attrval) > int(validrange[1])): + raise Exception(msg + ' Valid range is [%s,%s]' + %(validrange[0], validrange[1])) + if attrname == 'bond-mode' and attrval == '802.3ad': + dattrname = 'bond-min-links' + min_links = ifaceobj.get_attr_value_first(dattrname) + if not min_links or min_links == '0': + self.logger.warn('%s: required attribute %s' + %(ifaceobj.name, dattrname) + + ' not present or set to \'0\'') + elif attrname in ['bond-lacp-bypass-allow']: + # For some attrs, set default values + optiondict = self.get_mod_attr(attrname) + if optiondict: + return optiondict.get('default') + return attrval + + def _apply_master_settings(self, ifaceobj): + have_attrs_to_set = 0 + linkup = False + ifenslavecmd_attrmap = OrderedDict([('bond-mode' , 'mode'), + ('bond-miimon' , 'miimon'), + ('bond-use-carrier', 'use_carrier'), + ('bond-lacp-rate' , 'lacp_rate'), + ('bond-xmit-hash-policy' , 'xmit_hash_policy'), + ('bond-min-links' , 'min_links'), + ('bond-num-grat-arp' , 'num_grat_arp'), + ('bond-num-unsol-na' , 'num_unsol_na'), + ('bond-ad-sys-mac-addr' , 'ad_sys_mac_addr'), + ('bond-ad-sys-priority' , 'ad_sys_priority'), + ('bond-lacp-fallback-allow', 'lacp_bypass_allow'), + ('bond-lacp-fallback-period', 'lacp_bypass_period'), + ('bond-lacp-bypass-allow', 'lacp_bypass_allow'), + ('bond-lacp-bypass-period', 'lacp_bypass_period')]) + linkup = self.ipcmd.is_link_up(ifaceobj.name) + try: + # order of attributes set matters for bond, so + # construct the list sequentially + attrstoset = OrderedDict() + for k, dstk in ifenslavecmd_attrmap.items(): + v = self.fetch_attr(ifaceobj, k) + if v: + attrstoset[dstk] = v + if not attrstoset: + return + have_attrs_to_set = 1 + self.ifenslavecmd.set_attrs(ifaceobj.name, attrstoset, + self.ipcmd.link_down if linkup else None) + except: + raise + finally: + if have_attrs_to_set and linkup: + self.ipcmd.link_up(ifaceobj.name) + + def _add_slaves(self, ifaceobj): + runningslaves = [] + + slaves = self._get_slave_list(ifaceobj) + if not slaves: + self.logger.debug('%s: no slaves found' %ifaceobj.name) + return + + if not self.PERFMODE: + runningslaves = self.ifenslavecmd.get_slaves(ifaceobj.name); + if runningslaves: + # Delete active slaves not in the new slave list + [ self.ifenslavecmd.remove_slave(ifaceobj.name, s) + for s in runningslaves if s not in slaves ] + + for slave in Set(slaves).difference(Set(runningslaves)): + if not self.PERFMODE and not self.ipcmd.link_exists(slave): + self.log_warn('%s: skipping slave %s, does not exist' + %(ifaceobj.name, slave)) + continue + link_up = False + if self.ipcmd.is_link_up(slave): + rtnetlink_api.rtnl_api.link_set(slave, "down") + link_up = True + self.ipcmd.link_set(slave, 'master', ifaceobj.name) + if link_up or ifaceobj.link_type != ifaceLinkType.LINK_NA: + try: + rtnetlink_api.rtnl_api.link_set(slave, "up") + except Exception, e: + self.logger.debug('%s: %s: link set up (%s)' + %(ifaceobj.name, slave, str(e))) + pass + + def _set_clag_enable(self, ifaceobj): + attrval = ifaceobj.get_attr_value_first('clag-id') + attrval = attrval if attrval else '0' + self.ifenslavecmd.set_clag_enable(ifaceobj.name, attrval) + + def _apply_slaves_lacp_bypass_prio(self, ifaceobj): + slaves = self.ifenslavecmd.get_slaves(ifaceobj.name) + if not slaves: + return + attrval = ifaceobj.get_attrs_value_first(['bond-lacp-bypass-priority', + 'bond-lacp-fallback-priority']) + if attrval: + portlist = self.parse_port_list(attrval) + if not portlist: + self.log_warn('%s: could not parse \'%s %s\'' + %(ifaceobj.name, attrname, attrval)) + return + for p in portlist: + try: + (port, val) = p.split('=') + if port not in slaves: + self.log_warn('%s: skipping slave %s, does not exist' + %(ifaceobj.name, port)) + continue + slaves.remove(port) + self.ifenslavecmd.set_lacp_fallback_priority( + ifaceobj.name, port, val) + except Exception, e: + self.log_warn('%s: failed to set lacp_fallback_priority %s (%s)' + %(ifaceobj.name, port, str(e))) + + for p in slaves: + try: + self.ifenslavecmd.set_lacp_fallback_priority(ifaceobj.name, p, '0') + except Exception, e: + self.log_warn('%s: failed to clear lacp_bypass_priority %s (%s)' + %(ifaceobj.name, p, str(e))) + + + def _up(self, ifaceobj): + try: + if not self.ipcmd.link_exists(ifaceobj.name): + self.ifenslavecmd.create_bond(ifaceobj.name) + self._apply_master_settings(ifaceobj) + # clag_enable has to happen before the slaves are added to the bond + self._set_clag_enable(ifaceobj) + self._add_slaves(ifaceobj) + self._apply_slaves_lacp_bypass_prio(ifaceobj) + if ifaceobj.addr_method == 'manual': + rtnetlink_api.rtnl_api.link_set(ifaceobj.name, "up") + except Exception, e: + self.log_error(str(e)) + + def _down(self, ifaceobj): + try: + self.ifenslavecmd.delete_bond(ifaceobj.name) + except Exception, e: + self.log_warn(str(e)) + + def _query_check(self, ifaceobj, ifaceobjcurr): + slaves = None + + if not self.ifenslavecmd.bond_exists(ifaceobj.name): + self.logger.debug('bond iface %s' %ifaceobj.name + + ' does not exist') + return + + ifaceattrs = self.dict_key_subset(ifaceobj.config, + self.get_mod_attrs()) + if not ifaceattrs: return + runningattrs = self._query_running_attrs(ifaceobj.name) + + # backward compat change + runningattrs.update({'bond-lacp-fallback-allow': runningattrs.get( + 'bond-lacp-bypass-allow'), + 'bond-lacp-fallback-period': runningattrs.get( + 'bond-lacp-bypass-period'), + 'bond-lacp-fallback-priority': runningattrs.get( + 'bond-lacp-bypass-priority')}) + for k in ifaceattrs: + v = ifaceobj.get_attr_value_first(k) + if not v: + continue + if k == 'bond-slaves': + slaves = self._get_slave_list(ifaceobj) + continue + rv = runningattrs.get(k) + if not rv: + ifaceobjcurr.update_config_with_status(k, 'None', 1) + else: + if (k == 'bond-lacp-bypass-priority' or + k == 'bond-lacp-fallback-priority'): + prios = v.split() + prios.sort() + prio_str = ' '.join(prios) + ifaceobjcurr.update_config_with_status(k, rv, + 1 if prio_str != rv else 0) + continue + ifaceobjcurr.update_config_with_status(k, rv, + 1 if v != rv else 0) + runningslaves = runningattrs.get('bond-slaves') + if not slaves and not runningslaves: + return + retslave = 1 + if slaves and runningslaves: + if slaves and runningslaves: + difference = set(slaves).symmetric_difference(runningslaves) + if not difference: + retslave = 0 + ifaceobjcurr.update_config_with_status('bond-slaves', + ' '.join(runningslaves) + if runningslaves else 'None', retslave) + + def _query_running_attrs(self, bondname): + bondattrs = {'bond-mode' : + self.ifenslavecmd.get_mode(bondname), + 'bond-miimon' : + self.ifenslavecmd.get_miimon(bondname), + 'bond-use-carrier' : + self.ifenslavecmd.get_use_carrier(bondname), + 'bond-lacp-rate' : + self.ifenslavecmd.get_lacp_rate(bondname), + 'bond-min-links' : + self.ifenslavecmd.get_min_links(bondname), + 'bond-ad-sys-mac-addr' : + self.ifenslavecmd.get_ad_sys_mac_addr(bondname), + 'bond-ad-sys-priority' : + self.ifenslavecmd.get_ad_sys_priority(bondname), + 'bond-xmit-hash-policy' : + self.ifenslavecmd.get_xmit_hash_policy(bondname), + 'bond-lacp-bypass-allow' : + self.ifenslavecmd.get_lacp_fallback_allow(bondname), + 'bond-lacp-bypass-period' : + self.ifenslavecmd.get_lacp_fallback_period(bondname), + 'bond-lacp-bypass-priority' : + self.ifenslavecmd.get_lacp_fallback_priority(bondname)} + slaves = self.ifenslavecmd.get_slaves(bondname) + if slaves: + bondattrs['bond-slaves'] = slaves + return bondattrs + + def _query_running(self, ifaceobjrunning): + if not self.ifenslavecmd.bond_exists(ifaceobjrunning.name): + return + bondattrs = self._query_running_attrs(ifaceobjrunning.name) + if bondattrs.get('bond-slaves'): + bondattrs['bond-slaves'] = ' '.join(bondattrs.get('bond-slaves')) + [ifaceobjrunning.update_config(k, v) + for k, v in bondattrs.items() + if v and v != self.get_mod_subattr(k, 'default')] + + _run_ops = {'pre-up' : _up, + 'post-down' : _down, + 'query-running' : _query_running, + 'query-checkcurr' : _query_check} + + def get_ops(self): + """ returns list of ops supported by this module """ + return self._run_ops.keys() + + def _init_command_handlers(self): + flags = self.get_flags() + if not self.ipcmd: + self.ipcmd = iproute2(**flags) + if not self.ifenslavecmd: + self.ifenslavecmd = ifenslaveutil(**flags) + + def run(self, ifaceobj, operation, query_ifaceobj=None, **extra_args): + """ run bond configuration on the interface object passed as argument + + Args: + **ifaceobj** (object): iface object + + **operation** (str): any of 'pre-up', 'post-down', 'query-checkcurr', + 'query-running' + + Kwargs: + **query_ifaceobj** (object): query check ifaceobject. This is only + valid when op is 'query-checkcurr'. It is an object same as + ifaceobj, but contains running attribute values and its config + status. The modules can use it to return queried running state + of interfaces. status is success if the running state is same + as user required state in ifaceobj. error otherwise. + """ + op_handler = self._run_ops.get(operation) + if not op_handler: + return + if operation != 'query-running' and not self._is_bond(ifaceobj): + return + self._init_command_handlers() + if operation == 'query-checkcurr': + op_handler(self, ifaceobj, query_ifaceobj) + else: + op_handler(self, ifaceobj) diff --git a/ifupdown2/addons/loopback.py b/ifupdown2/addons/loopback.py new file mode 100644 index 0000000..dd35917 --- /dev/null +++ b/ifupdown2/addons/loopback.py @@ -0,0 +1,63 @@ +#!/usr/bin/python + +# This should be pretty simple and might not really even need to exist. +# The key is that we need to call link_create with a type of "dummy" +# since that will translate to 'ip link add loopbackX type dummy' +# The config file should probably just indicate that the type is +# loopback or dummy. + +from ifupdown.iface import * +from ifupdownaddons.modulebase import moduleBase +from ifupdownaddons.iproute2 import iproute2 +import logging + +class loopback(moduleBase): + _modinfo = {'mhelp' : 'configure extra loopback module based on ' + + 'dummy device' } + + def __init__(self, *args, **kargs): + moduleBase.__init__(self, *args, **kargs) + self.ipcmd = None + + def _is_loopback_by_name(self, ifacename): + return 'loop' in ifacename + + def _up(self, ifaceobj): + if self._is_loopback_by_name(ifaceobj.name): + self.ipcmd.link_create(ifaceobj.name, 'dummy') + + def _down(self, ifaceobj): + if not self.PERFMODE and not self.ipcmd.link_exists(ifaceobj.name): + return + try: + self.ipcmd.link_delete(ifaceobj.name) + except Exception, e: + self.log_warn(str(e)) + + def _query_check(self, ifaceobj, ifaceobjcurr): + if not self.ipcmd.link_exists(ifaceobj.name): + return + + _run_ops = {'pre-up' : _up, + 'post-down' : _down, + 'query-checkcurr' : _query_check} + + def get_ops(self): + return self._run_ops.keys() + + def _init_command_handlers(self): + if not self.ipcmd: + self.ipcmd = iproute2(**self.get_flags()) + + def run(self, ifaceobj, operation, query_ifaceobj=None, **extra_args): + op_handler = self._run_ops.get(operation) + if not op_handler: + return + if (operation != 'query-running' and + not self._is_loopback_by_name(ifaceobj.name)): + return + self._init_command_handlers() + if operation == 'query-checkcurr': + op_handler(self, ifaceobj, query_ifaceobj) + else: + op_handler(self, ifaceobj) diff --git a/ifupdown2/addons/mstpctl.py b/ifupdown2/addons/mstpctl.py new file mode 100644 index 0000000..33ea693 --- /dev/null +++ b/ifupdown2/addons/mstpctl.py @@ -0,0 +1,770 @@ +#!/usr/bin/python +# +# Copyright 2014 Cumulus Networks, Inc. All rights reserved. +# Author: Roopa Prabhu, roopa@cumulusnetworks.com +# + +from sets import Set +from ifupdown.iface import * +from ifupdownaddons.modulebase import moduleBase +from ifupdownaddons.bridgeutils import brctl +from ifupdownaddons.iproute2 import iproute2 +from ifupdownaddons.mstpctlutil import mstpctlutil +import traceback + +class mstpctl(moduleBase): + """ ifupdown2 addon module to configure mstp attributes """ + + _modinfo = {'mhelp' : 'mstp configuration module for bridges', + 'attrs' : { + 'mstpctl-ports' : + {'help' : 'mstp ports', + 'compat' : True}, + 'mstpctl-stp' : + {'help': 'bridge stp yes/no', + 'compat' : True, + 'default' : 'no'}, + 'mstpctl-treeprio' : + {'help': 'tree priority', + 'default' : '32768', + 'validrange' : ['0', '65535'], + 'required' : False, + 'example' : ['mstpctl-treeprio 32768']}, + 'mstpctl-ageing' : + {'help': 'ageing time', + 'default' : '300', + 'required' : False, + 'example' : ['mstpctl-ageing 300']}, + 'mstpctl-maxage' : + { 'help' : 'max message age', + 'default' : '20', + 'required' : False, + 'example' : ['mstpctl-maxage 20']}, + 'mstpctl-fdelay' : + { 'help' : 'set forwarding delay', + 'default' : '15', + 'required' : False, + 'example' : ['mstpctl-fdelay 15']}, + 'mstpctl-maxhops' : + { 'help' : 'bridge max hops', + 'default' : '15', + 'required' : False, + 'example' : ['mstpctl-maxhops 15']}, + 'mstpctl-txholdcount' : + { 'help' : 'bridge transmit holdcount', + 'default' : '6', + 'required' : False, + 'example' : ['mstpctl-txholdcount 6']}, + 'mstpctl-forcevers' : + { 'help' : 'bridge force stp version', + 'default' : 'rstp', + 'required' : False, + 'example' : ['mstpctl-forcevers rstp']}, + 'mstpctl-portpathcost' : + { 'help' : 'bridge port path cost', + 'default' : '0', + 'required' : False, + 'example' : ['mstpctl-portpathcost swp1=0 swp2=1']}, + 'mstpctl-portp2p' : + { 'help' : 'bridge port p2p detection mode', + 'default' : 'auto', + 'validvals' : ['yes', 'no', 'auto'], + 'required' : False, + 'example' : ['mstpctl-portp2p swp1=no swp2=no']}, + 'mstpctl-portrestrrole' : + { 'help' : + 'enable/disable port ability to take root role of the port', + 'default' : 'no', + 'validvals' : ['yes', 'no'], + 'required' : False, + 'example' : ['mstpctl-portrestrrole swp1=no swp2=no']}, + 'mstpctl-portrestrtcn' : + { 'help' : + 'enable/disable port ability to propagate received topology change notification of the port', + 'default' : 'no', + 'validvals' : ['yes', 'no'], + 'required' : False, + 'example' : ['mstpctl-portrestrtcn swp1=no swp2=no']}, + 'mstpctl-bpduguard' : + { 'help' : + 'enable/disable bpduguard', + 'default' : 'no', + 'validvals' : ['yes', 'no'], + 'required' : False, + 'example' : ['mstpctl-bpduguard swp1=no swp2=no']}, + 'mstpctl-treeportprio' : + { 'help' : + 'port priority for MSTI instance', + 'default' : '128', + 'validrange' : ['0', '240'], + 'required' : False, + 'example' : ['mstpctl-treeportprio swp1=128 swp2=128']}, + 'mstpctl-hello' : + { 'help' : 'set hello time', + 'default' : '2', + 'required' : False, + 'example' : ['mstpctl-hello 2']}, + 'mstpctl-portnetwork' : + { 'help' : 'enable/disable bridge assurance capability for a port', + 'validvals' : ['yes', 'no'], + 'default' : 'no', + 'required' : False, + 'example' : ['mstpctl-portnetwork swp1=no swp2=no']}, + 'mstpctl-portadminedge' : + { 'help' : 'enable/disable initial edge state of the port', + 'validvals' : ['yes', 'no'], + 'default' : 'no', + 'required' : False, + 'example' : ['mstpctl-portadminedge swp1=no swp2=no']}, + 'mstpctl-portautoedge' : + { 'help' : 'enable/disable auto transition to/from edge state of the port', + 'validvals' : ['yes', 'no'], + 'default' : 'no', + 'required' : False, + 'example' : ['mstpctl-portautoedge swp1=yes swp2=yes']}, + 'mstpctl-treeportcost' : + { 'help' : 'port tree cost', + 'required' : False}, + 'mstpctl-portbpdufilter' : + { 'help' : 'enable/disable bpdu filter on a port. ' + + 'syntax varies when defined under a bridge ' + + 'vs under a port', + 'validvals' : ['yes', 'no'], + 'default' : 'no', + 'required' : False, + 'example' : ['under a bridge: mstpctl-portbpdufilter swp1=no swp2=no', + 'under a port: mstpctl-portbpdufilter yes']}, + }} + + # Maps mstp bridge attribute names to corresponding mstpctl commands + # XXX: This can be encoded in the modules dict above + _attrs_map = OrderedDict([('mstpctl-treeprio' , 'treeprio'), + ('mstpctl-ageing' , 'ageing'), + ('mstpctl-maxage' , 'maxage'), + ('mstpctl-fdelay' , 'fdelay'), + ('mstpctl-maxhops' , 'maxhops'), + ('mstpctl-txholdcount' , 'txholdcount'), + ('mstpctl-forcevers', 'forcevers'), + ('mstpctl-hello' , 'hello')]) + + # Maps mstp port attribute names to corresponding mstpctl commands + # XXX: This can be encoded in the modules dict above + _port_attrs_map = {'mstpctl-portpathcost' : 'portpathcost', + 'mstpctl-portadminedge' : 'portadminedge', + 'mstpctl-portautoedge' : 'portautoedge' , + 'mstpctl-portp2p' : 'portp2p', + 'mstpctl-portrestrrole' : 'portrestrrole', + 'mstpctl-portrestrtcn' : 'portrestrtcn', + 'mstpctl-bpduguard' : 'bpduguard', + 'mstpctl-treeportprio' : 'treeportprio', + 'mstpctl-treeportcost' : 'treeportcost', + 'mstpctl-portnetwork' : 'portnetwork', + 'mstpctl-portbpdufilter' : 'portbpdufilter'} + + # declare some ifaceobj priv_flags. + # XXX: This assumes that the priv_flags is owned by this module + # which it is not. + _BRIDGE_PORT_PROCESSED = 0x1 + + def __init__(self, *args, **kargs): + moduleBase.__init__(self, *args, **kargs) + self.ipcmd = None + self.brctlcmd = None + self.mstpctlcmd = None + + def _is_bridge(self, ifaceobj): + if (ifaceobj.get_attr_value_first('mstpctl-ports') or + ifaceobj.get_attr_value_first('bridge-ports')): + return True + return False + + def _is_bridge_port(self, ifaceobj): + if self.brctlcmd.is_bridge_port(ifaceobj.name): + return True + return False + + def get_dependent_ifacenames(self, ifaceobj, ifacenames_all=None): + if not self._is_bridge(ifaceobj): + return None + return self.parse_port_list(ifaceobj.get_attr_value_first( + 'mstpctl-ports'), ifacenames_all) + + def get_dependent_ifacenames_running(self, ifaceobj): + self._init_command_handlers() + if (self.brctlcmd.bridge_exists(ifaceobj.name) and + not self.mstpctlcmd.mstpbridge_exists(ifaceobj.name)): + return None + return self.brctlcmd.get_bridge_ports(ifaceobj.name) + + def _get_bridge_port_list(self, ifaceobj): + + # port list is also available in the previously + # parsed dependent list. Use that if available, instead + # of parsing port expr again + port_list = ifaceobj.lowerifaces + if port_list: + return port_list + ports = ifaceobj.get_attr_value_first('mstpctl-ports') + if ports: + return self.parse_port_list(ports) + else: + return None + + def _ports_enable_disable_ipv6(self, ports, enable='1'): + for p in ports: + try: + self.write_file('/proc/sys/net/ipv6/conf/%s' %p + + '/disable_ipv6', enable) + except Exception, e: + self.logger.info(str(e)) + pass + + def _add_ports(self, ifaceobj): + bridgeports = self._get_bridge_port_list(ifaceobj) + + runningbridgeports = [] + # Delete active ports not in the new port list + if not self.PERFMODE: + runningbridgeports = self.brctlcmd.get_bridge_ports(ifaceobj.name) + if runningbridgeports: + [self.ipcmd.link_set(bport, 'nomaster') + for bport in runningbridgeports + if not bridgeports or bport not in bridgeports] + else: + runningbridgeports = [] + if not bridgeports: + return + err = 0 + for bridgeport in Set(bridgeports).difference(Set(runningbridgeports)): + try: + if not self.DRYRUN and not self.ipcmd.link_exists(bridgeport): + self.log_warn('%s: bridge port %s does not exist' + %(ifaceobj.name, bridgeport)) + err += 1 + continue + self.ipcmd.link_set(bridgeport, 'master', ifaceobj.name) + self.ipcmd.addr_flush(bridgeport) + except Exception, e: + self.log_error(str(e)) + + if err: + self.log_error('error configuring bridge (missing ports)') + + def _apply_bridge_settings(self, ifaceobj): + check = False if self.PERFMODE else True + try: + # set bridge attributes + for attrname, dstattrname in self._attrs_map.items(): + try: + v = ifaceobj.get_attr_value_first(attrname) + if not v: + continue + if attrname == 'mstpctl-treeprio': + self.mstpctlcmd.set_bridge_treeprio(ifaceobj.name, + v, check) + else: + self.mstpctlcmd.set_bridge_attr(ifaceobj.name, + dstattrname, v, check) + except Exception, e: + self.logger.warn('%s' %str(e)) + pass + + # set bridge port attributes + for attrname, dstattrname in self._port_attrs_map.items(): + attrval = ifaceobj.get_attr_value_first(attrname) + if not attrval: + continue + portlist = self.parse_port_list(attrval) + if not portlist: + self.log_warn('%s: error parsing \'%s %s\'' + %(ifaceobj.name, attrname, attrval)) + continue + for p in portlist: + try: + (port, val) = p.split('=') + self.mstpctlcmd.set_bridgeport_attr(ifaceobj.name, + port, dstattrname, val, check) + except Exception, e: + self.log_warn('%s: error setting %s (%s)' + %(ifaceobj.name, attrname, str(e))) + except Exception, e: + self.log_warn(str(e)) + pass + + def _apply_bridge_port_settings(self, ifaceobj, bridgename=None, + bridgeifaceobj=None, stp_on=True, + mstpd_running=True): + check = False if self.PERFMODE else True + if not bridgename and bridgeifaceobj: + bridgename = bridgeifaceobj.name + # set bridge port attributes + for attrname, dstattrname in self._port_attrs_map.items(): + attrval = ifaceobj.get_attr_value_first(attrname) + if not attrval: + #if bridgeifaceobj: + # # If bridge object available, check if the bridge + # # has the attribute set, in which case, + # # inherit it from the bridge + # attrval = bridgeifaceobj.get_attr_value_first(attrname) + # if not attrval: + # continue + #else: + continue + if not stp_on: + self.logger.warn('%s: cannot set %s (stp on bridge %s not on)\n' + %(ifaceobj.name, attrname, bridgename)) + continue + if not mstpd_running: + continue + try: + self.mstpctlcmd.set_bridgeport_attr(bridgename, + ifaceobj.name, dstattrname, attrval, check) + except Exception, e: + self.log_warn('%s: error setting %s (%s)' + %(ifaceobj.name, attrname, str(e))) + + def _apply_bridge_port_settings_all(self, ifaceobj, + ifaceobj_getfunc=None): + self.logger.info('%s: applying mstp configuration ' + %ifaceobj.name + 'specific to ports') + # Query running bridge ports. and only apply attributes on them + bridgeports = self.brctlcmd.get_bridge_ports(ifaceobj.name) + if not bridgeports: + self.logger.debug('%s: cannot find bridgeports' %ifaceobj.name) + return + for bport in bridgeports: + self.logger.info('%s: processing mstp config for port %s' + %(ifaceobj.name, bport)) + if not self.ipcmd.link_exists(bport): + continue + bportifaceobjlist = ifaceobj_getfunc(bport) + if not bportifaceobjlist: + continue + for bportifaceobj in bportifaceobjlist: + # Dont process bridge port if it already has been processed + if bportifaceobj.priv_flags & self._BRIDGE_PORT_PROCESSED: + continue + try: + self._apply_bridge_port_settings(bportifaceobj, + ifaceobj.name, ifaceobj) + except Exception, e: + self.log_warn(str(e)) + + def _up(self, ifaceobj, ifaceobj_getfunc=None): + # Check if bridge port + bridgename = self.ipcmd.bridge_port_get_bridge_name(ifaceobj.name) + if bridgename: + mstpd_running = (True if self.mstpctlcmd.is_mstpd_running() + else False) + stp_on = (True if self.read_file_oneline( + '/sys/class/net/%s/bridge/stp_state' + %bridgename) == '2' else False) + self._apply_bridge_port_settings(ifaceobj, bridgename, None, + stp_on, mstpd_running) + ifaceobj.priv_flags |= self._BRIDGE_PORT_PROCESSED + return + if not self._is_bridge(ifaceobj): + return + stp = None + try: + porterr = False + porterrstr = '' + if ifaceobj.get_attr_value_first('mstpctl-ports'): + # If bridge ports specified with mstpctl attr, create the + # bridge and also add its ports + self.ipcmd.batch_start() + if not self.PERFMODE: + if not self.ipcmd.link_exists(ifaceobj.name): + self.ipcmd.link_create(ifaceobj.name, 'bridge') + else: + self.ipcmd.link_create(ifaceobj.name, 'bridge') + try: + self._add_ports(ifaceobj) + except Exception, e: + porterr = True + porterrstr = str(e) + pass + finally: + self.ipcmd.batch_commit() + running_ports = self.brctlcmd.get_bridge_ports(ifaceobj.name) + if running_ports: + # disable ipv6 for ports that were added to bridge + self._ports_enable_disable_ipv6(running_ports, '1') + + stp = ifaceobj.get_attr_value_first('mstpctl-stp') + if stp: + self.set_iface_attr(ifaceobj, 'mstpctl-stp', + self.brctlcmd.set_stp) + else: + stp = self.brctlcmd.get_stp(ifaceobj.name) + if (self.mstpctlcmd.is_mstpd_running() and + (stp == 'yes' or stp == 'on')): + self._apply_bridge_settings(ifaceobj) + self._apply_bridge_port_settings_all(ifaceobj, + ifaceobj_getfunc=ifaceobj_getfunc) + except Exception, e: + self.log_error(str(e)) + if porterr: + raise Exception(porterrstr) + + def _down(self, ifaceobj, ifaceobj_getfunc=None): + if not self._is_bridge(ifaceobj): + return + try: + if ifaceobj.get_attr_value_first('mstpctl-ports'): + # If bridge ports specified with mstpctl attr, delete the + # bridge + ports = self.brctlcmd.get_bridge_ports(ifaceobj.name) + if ports: + self._ports_enable_disable_ipv6(ports, '0') + self.brctlcmd.delete_bridge(ifaceobj.name) + except Exception, e: + self.log_error(str(e)) + + def _query_running_attrs(self, ifaceobjrunning): + bridgeattrdict = {} + + tmpbridgeattrdict = self.mstpctlcmd.get_bridge_attrs(ifaceobjrunning.name) + if not tmpbridgeattrdict: + return bridgeattrdict + + for k,v in tmpbridgeattrdict.items(): + if k == 'stp' or not v: + continue + if k == 'ports': + ports = v.keys() + continue + attrname = 'mstpctl-' + k + if v and v != self.get_mod_subattr(attrname, 'default'): + bridgeattrdict[attrname] = [v] + + ports = self.brctlcmd.get_bridge_ports(ifaceobjrunning.name) + if ports: + portconfig = {'mstpctl-portnetwork' : '', + 'mstpctl-portpathcost' : '', + 'mstpctl-portadminedge' : '', + 'mstpctl-portautoedge' : '', + 'mstpctl-portp2p' : '', + 'mstpctl-portrestrrole' : '', + 'mstpctl-portrestrtcn' : '', + 'mstpctl-bpduguard' : '', + 'mstpctl-treeportprio' : '', + 'mstpctl-treeportcost' : ''} + + for p in ports: + v = self.mstpctlcmd.get_bridgeport_attr(ifaceobjrunning.name, + p, 'portnetwork') + if v and v != 'no': + portconfig['mstpctl-portnetwork'] += ' %s=%s' %(p, v) + + # XXX: Can we really get path cost of a port ??? + #v = self.mstpctlcmd.get_portpathcost(ifaceobjrunning.name, p) + #if v and v != self.get_mod_subattr('mstpctl-portpathcost', + # 'default'): + # portconfig['mstpctl-portpathcost'] += ' %s=%s' %(p, v) + + v = self.mstpctlcmd.get_bridgeport_attr(ifaceobjrunning.name, + p, 'portadminedge') + if v and v != 'no': + portconfig['mstpctl-portadminedge'] += ' %s=%s' %(p, v) + + v = self.mstpctlcmd.get_bridgeport_attr(ifaceobjrunning.name, + p, 'portp2p') + if v and v != 'no': + portconfig['mstpctl-portp2p'] += ' %s=%s' %(p, v) + + v = self.mstpctlcmd.get_bridgeport_attr(ifaceobjrunning.name, + p, 'portrestrrole') + if v and v != 'no': + portconfig['mstpctl-portrestrrole'] += ' %s=%s' %(p, v) + + v = self.mstpctlcmd.get_bridgeport_attr(ifaceobjrunning.name, + p, 'portrestrtcn') + if v and v != 'no': + portconfig['mstpctl-portrestrtcn'] += ' %s=%s' %(p, v) + + v = self.mstpctlcmd.get_bridgeport_attr(ifaceobjrunning.name, + p, 'bpduguard') + if v and v != 'no': + portconfig['mstpctl-bpduguard'] += ' %s=%s' %(p, v) + + # XXX: Can we really get path cost of a port ??? + #v = self.mstpctlcmd.get_bridgeport_attr(ifaceobjrunning.name, + # p, 'treeprio') + #if v and v != self.get_mod_subattr('mstpctl-treeportprio', + # 'default'): + # portconfig['mstpctl-treeportprio'] += ' %s=%s' %(p, v) + + #v = self.mstpctlcmd.get_bridgeport_attr(ifaceobjrunning.name, + # p, 'treecost') + #if v and v != self.get_mod_subattr('mstpctl-treeportcost', + # 'default'): + # portconfig['mstpctl-treeportcost'] += ' %s=%s' %(p, v) + + bridgeattrdict.update({k : [v] for k, v in portconfig.items() + if v}) + return bridgeattrdict + + def _query_check_bridge(self, ifaceobj, ifaceobjcurr): + # list of attributes that are not supported currently + blacklistedattrs = ['mstpctl-portpathcost', + 'mstpctl-treeportprio', 'mstpctl-treeportcost'] + if not self.brctlcmd.bridge_exists(ifaceobj.name): + self.logger.debug('bridge %s does not exist' %ifaceobj.name) + return + ifaceattrs = self.dict_key_subset(ifaceobj.config, + self.get_mod_attrs()) + if not ifaceattrs: + return + runningattrs = self.mstpctlcmd.get_bridge_attrs(ifaceobj.name) + if not runningattrs: + runningattrs = {} + for k in ifaceattrs: + # for all mstpctl options + if k in blacklistedattrs: + continue + # get the corresponding ifaceobj attr + v = ifaceobj.get_attr_value_first(k) + if not v: + continue + + # Get the running attribute + rv = runningattrs.get(k[8:]) + if k == 'mstpctl-stp': + # special case stp compare because it may + # contain more than one valid values + stp_on_vals = ['on', 'yes'] + stp_off_vals = ['off'] + rv = self.brctlcmd.get_stp(ifaceobj.name) + if ((v in stp_on_vals and rv in stp_on_vals) or + (v in stp_off_vals and rv in stp_off_vals)): + ifaceobjcurr.update_config_with_status('mstpctl-stp', v, 0) + else: + ifaceobjcurr.update_config_with_status('mstpctl-stp', v, 1) + continue + + if k == 'mstpctl-ports': + # special case ports because it can contain regex or glob + # XXX: We get all info from mstputils, which means if + # mstpd is down, we will not be returning any bridge bridgeports + running_port_list = self.brctlcmd.get_bridge_ports(ifaceobj.name) + bridge_port_list = self._get_bridge_port_list(ifaceobj) + if not running_port_list and not bridge_port_list: + continue + portliststatus = 1 + if running_port_list and bridge_port_list: + difference = Set(running_port_list).symmetric_difference( + Set(bridge_port_list)) + if not difference: + portliststatus = 0 + ifaceobjcurr.update_config_with_status('mstpctl-ports', + ' '.join(running_port_list) + if running_port_list else '', portliststatus) + elif k[:12] == 'mstpctl-port' or k == 'mstpctl-bpduguard': + # Now, look at port attributes + # derive the mstpctlcmd attr name + #mstpctlcmdattrname = k[12:] if k[:12] == 'mstpctl-port' else k[8:] + mstpctlcmdattrname = k[8:] + + # for port attributes, the attributes are in a list + # = + status = 0 + currstr = '' + vlist = self.parse_port_list(v) + if not vlist: + continue + for vlistitem in vlist: + try: + (p, v) = vlistitem.split('=') + currv = self.mstpctlcmd.get_bridgeport_attr( + ifaceobj.name, p, mstpctlcmdattrname) + if currv: + currstr += ' %s=%s' %(p, currv) + else: + currstr += ' %s=%s' %(p, 'None') + if currv != v: + status = 1 + except Exception, e: + self.log_warn(str(e)) + pass + ifaceobjcurr.update_config_with_status(k, currstr, status) + elif not rv: + ifaceobjcurr.update_config_with_status(k, '', 1) + elif v != rv: + ifaceobjcurr.update_config_with_status(k, rv, 1) + else: + ifaceobjcurr.update_config_with_status(k, rv, 0) + + def _query_check_bridge_port(self, ifaceobj, ifaceobjcurr): + if not self.ipcmd.link_exists(ifaceobj.name): + #self.logger.debug('bridge port %s does not exist' %ifaceobj.name) + ifaceobjcurr.status = ifaceStatus.NOTFOUND + return + # Check if this is a bridge port + if not self._is_bridge_port(ifaceobj): + # mark all the bridge attributes as error + ifaceobjcurr.check_n_update_config_with_status_many(ifaceobj, + self._port_attrs_map.keys(), 0) + return + bridgename = self.ipcmd.bridge_port_get_bridge_name(ifaceobj.name) + # list of attributes that are not supported currently + blacklistedattrs = ['mstpctl-portpathcost', + 'mstpctl-treeportprio', 'mstpctl-treeportcost'] + ifaceattrs = self.dict_key_subset(ifaceobj.config, + self._port_attrs_map.keys()) + if not ifaceattrs: + return + runningattrs = self.mstpctlcmd.get_bridge_attrs(ifaceobj.name) + if not runningattrs: + runningattrs = {} + for k in ifaceattrs: + # for all mstpctl options + # get the corresponding ifaceobj attr + v = ifaceobj.get_attr_value_first(k) + if not v or k in blacklistedattrs: + ifaceobjcurr.update_config_with_status(k, v, -1) + continue + currv = self.mstpctlcmd.get_bridgeport_attr(bridgename, + ifaceobj.name, self._port_attrs_map.get(k)) + if currv: + if currv != v: + ifaceobjcurr.update_config_with_status(k, currv, 1) + else: + ifaceobjcurr.update_config_with_status(k, currv, 0) + else: + ifaceobjcurr.update_config_with_status(k, None, 1) + + def _query_check(self, ifaceobj, ifaceobjcurr, ifaceobj_getfunc=None): + if self._is_bridge(ifaceobj): + self._query_check_bridge(ifaceobj, ifaceobjcurr) + else: + self._query_check_bridge_port(ifaceobj, ifaceobjcurr) + + def _query_running_bridge_port(self, ifaceobjrunning): + bridgename = self.ipcmd.bridge_port_get_bridge_name( + ifaceobjrunning.name) + if not bridgename: + self.logger.warn('%s: unable to determine bridgename' + %ifaceobjrunning.name) + return + if self.brctlcmd.get_stp(bridgename) == 'no': + # This bridge does not run stp, return + return + # if userspace stp not set, return + if self.sysctl_get('net.bridge.bridge-stp-user-space') != '1': + return + v = self.mstpctlcmd.get_bridgeport_attr(bridgename, + ifaceobjrunning.name, + 'portnetwork') + if v and v != 'no': + ifaceobjrunning.update_config('mstpctl-network', v) + + # XXX: Can we really get path cost of a port ??? + #v = self.mstpctlcmd.get_portpathcost(ifaceobjrunning.name, p) + #if v and v != self.get_mod_subattr('mstpctl-pathcost', + # 'default'): + # ifaceobjrunning.update_config('mstpctl-network', v) + + v = self.mstpctlcmd.get_bridgeport_attr(bridgename, + ifaceobjrunning.name, 'portadminedge') + if v and v != 'no': + ifaceobjrunning.update_config('mstpctl-portadminedge', v) + + v = self.mstpctlcmd.get_bridgeport_attr(bridgename, + ifaceobjrunning.name,'portp2p') + if v and v != 'auto': + ifaceobjrunning.update_config('mstpctl-portp2p', v) + + v = self.mstpctlcmd.get_bridgeport_attr(bridgename, + ifaceobjrunning.name, 'portrestrrole') + if v and v != 'no': + ifaceobjrunning.update_config('mstpctl-portrestrrole', v) + + v = self.mstpctlcmd.get_bridgeport_attr(bridgename, + ifaceobjrunning.name, 'restrtcn') + if v and v != 'no': + ifaceobjrunning.update_config('mstpctl-portrestrtcn', v) + + v = self.mstpctlcmd.get_bridgeport_attr(bridgename, + ifaceobjrunning.name, 'bpduguard') + if v and v != 'no': + ifaceobjrunning.update_config('mstpctl-bpduguard', v) + + # XXX: Can we really get path cost of a port ??? + #v = self.mstpctlcmd.get_bridgeport_attr(ifaceobjrunning.name, + # p, 'treeprio') + #if v and v != self.get_mod_subattr('mstpctl-treeportprio', + # 'default'): + # portconfig['mstpctl-treeportprio'] += ' %s=%s' %(p, v) + + #v = self.mstpctlcmd.get_bridgeport_attr(ifaceobjrunning.name, + # p, 'treecost') + #if v and v != self.get_mod_subattr('mstpctl-treeportcost', + # 'default'): + # portconfig['mstpctl-treeportcost'] += ' %s=%s' %(p, v) + + def _query_running_bridge(self, ifaceobjrunning): + if self.brctlcmd.get_stp(ifaceobjrunning.name) == 'no': + # This bridge does not run stp, return + return + # if userspace stp not set, return + if self.sysctl_get('net.bridge.bridge-stp-user-space') != '1': + return + # Check if mstp really knows about this bridge + if not self.mstpctlcmd.mstpbridge_exists(ifaceobjrunning.name): + return + ifaceobjrunning.update_config_dict(self._query_running_attrs( + ifaceobjrunning)) + + def _query_running(self, ifaceobjrunning, **extra_args): + if self.brctlcmd.bridge_exists(ifaceobjrunning.name): + self._query_running_bridge(ifaceobjrunning) + elif self.brctlcmd.is_bridge_port(ifaceobjrunning.name): + self._query_running_bridge_port(ifaceobjrunning) + + _run_ops = {'pre-up' : _up, + 'post-down' : _down, + 'query-checkcurr' : _query_check, + 'query-running' : _query_running} + + def get_ops(self): + """ returns list of ops supported by this module """ + return self._run_ops.keys() + + def _init_command_handlers(self): + flags = self.get_flags() + if not self.ipcmd: + self.ipcmd = iproute2(**flags) + if not self.brctlcmd: + self.brctlcmd = brctl(**flags) + if not self.mstpctlcmd: + self.mstpctlcmd = mstpctlutil(**flags) + + def run(self, ifaceobj, operation, query_ifaceobj=None, + ifaceobj_getfunc=None, **extra_args): + """ run mstp configuration on the interface object passed as argument + + Args: + **ifaceobj** (object): iface object + + **operation** (str): any of 'pre-up', 'post-down', 'query-checkcurr', + 'query-running' + Kwargs: + **query_ifaceobj** (object): query check ifaceobject. This is only + valid when op is 'query-checkcurr'. It is an object same as + ifaceobj, but contains running attribute values and its config + status. The modules can use it to return queried running state + of interfaces. status is success if the running state is same + as user required state in ifaceobj. error otherwise. + """ + if ifaceobj.type == ifaceType.BRIDGE_VLAN: + return + op_handler = self._run_ops.get(operation) + if not op_handler: + return + self._init_command_handlers() + if operation == 'query-checkcurr': + op_handler(self, ifaceobj, query_ifaceobj, + ifaceobj_getfunc=ifaceobj_getfunc) + else: + op_handler(self, ifaceobj, ifaceobj_getfunc=ifaceobj_getfunc) diff --git a/ifupdown2/addons/usercmds.py b/ifupdown2/addons/usercmds.py new file mode 100644 index 0000000..72915ea --- /dev/null +++ b/ifupdown2/addons/usercmds.py @@ -0,0 +1,97 @@ +#!/usr/bin/python +# +# Copyright 2014 Cumulus Networks, Inc. All rights reserved. +# Author: Roopa Prabhu, roopa@cumulusnetworks.com +# + +import subprocess +import ifupdownaddons + +class usercmds(ifupdownaddons.modulebase.moduleBase): + """ ifupdown2 addon module to configure user specified commands """ + + _modinfo = {'mhelp' : 'user commands for interfaces', + 'attrs' : { + 'pre-up' : + {'help' : 'run command before bringing the interface up'}, + 'up' : + {'help' : 'run command at interface bring up'}, + 'post-up' : + {'help' : 'run command after interface bring up'}, + 'pre-down' : + {'help' : 'run command before bringing the interface down'}, + 'down' : + {'help' : 'run command at interface down'}, + 'post-down' : + {'help' : 'run command after bringing interface down'}}} + + def _exec_user_cmd(self, cmd): + """ exec's commands using subprocess Popen + + special wrapper using use closefds=True and shell=True + for user commands + """ + + cmd_returncode = 0 + try: + self.logger.info('executing %s' %cmd) + if self.DRYRUN: + return + ch = subprocess.Popen(cmd, + stdout=subprocess.PIPE, + shell=True, + stderr=subprocess.STDOUT, + close_fds=True) + cmd_returncode = ch.wait() + cmdout = ch.communicate()[0] + except Exception, e: + raise Exception('failed to execute cmd \'%s\' (%s)' + %(cmd, str(e))) + if cmd_returncode != 0: + raise Exception(cmdout) + return cmdout + + def _run_command(self, ifaceobj, op): + cmd_list = ifaceobj.get_attr_value(op) + if cmd_list: + for cmd in cmd_list: + self.logger.info('executing cmd \'%s\'' %cmd) + try: + self._exec_user_cmd(cmd) + except Exception, e: + if not self.ignore_error(str(e)): + self.logger.warn('%s: %s cmd \'%s\' failed (%s)' + %(ifaceobj.name, op, cmd, str(e).strip('\n'))) + pass + + _run_ops = {'pre-up' : _run_command, + 'pre-down' : _run_command, + 'up' : _run_command, + 'post-up' : _run_command, + 'down' : _run_command, + 'post-down' : _run_command} + + def get_ops(self): + """ returns list of ops supported by this module """ + return self._run_ops.keys() + + def run(self, ifaceobj, operation, query_ifaceobj=None, **extra_args): + """ run user commands + + Args: + **ifaceobj** (object): iface object + + **operation** (str): list of ops + + Kwargs: + **query_ifaceobj** (object): query check ifaceobject. This is only + valid when op is 'query-checkcurr'. It is an object same as + ifaceobj, but contains running attribute values and its config + status. The modules can use it to return queried running state + of interfaces. status is success if the running state is same + as user required state in ifaceobj. error otherwise. + """ + op_handler = self._run_ops.get(operation) + if not op_handler: + return + op_handler(self, ifaceobj, operation) diff --git a/ifupdown2/addons/vlan.py b/ifupdown2/addons/vlan.py new file mode 100644 index 0000000..6e8e2fc --- /dev/null +++ b/ifupdown2/addons/vlan.py @@ -0,0 +1,236 @@ +#!/usr/bin/python +# +# Copyright 2014 Cumulus Networks, Inc. All rights reserved. +# Author: Roopa Prabhu, roopa@cumulusnetworks.com +# + +from ifupdown.iface import * +from ifupdownaddons.modulebase import moduleBase +from ifupdownaddons.iproute2 import iproute2 +import ifupdown.rtnetlink_api as rtnetlink_api +import logging +import re + +class vlan(moduleBase): + """ ifupdown2 addon module to configure vlans """ + + _modinfo = {'mhelp' : 'vlan module configures vlan interfaces.' + + 'This module understands vlan interfaces with dot ' + + 'notations. eg swp1.100. Vlan interfaces with any ' + + 'other names need to have raw device and vlan id ' + + 'attributes', + 'attrs' : { + 'vlan-raw-device' : + {'help' : 'vlan raw device'}, + 'vlan-id' : + {'help' : 'vlan id'}}} + + + def __init__(self, *args, **kargs): + moduleBase.__init__(self, *args, **kargs) + self.ipcmd = None + self._bridge_vids_query_cache = {} + self._resv_vlan_range = self._get_reserved_vlan_range() + self.logger.debug('%s: using reserved vlan range %s' + %(self.__class__.__name__, str(self._resv_vlan_range))) + + def _is_vlan_device(self, ifaceobj): + vlan_raw_device = ifaceobj.get_attr_value_first('vlan-raw-device') + if vlan_raw_device: + return True + elif '.' in ifaceobj.name: + return True + return False + + def _get_vlan_id(self, ifaceobj): + """ Derives vlanid from iface name + + Example: + Returns 1 for ifname vlan0001 returns 1 + Returns 1 for ifname vlan1 + Returns 1 for ifname eth0.1 + + Returns -1 if vlan id cannot be determined + """ + vid_str = ifaceobj.get_attr_value_first('vlan-id') + try: + if vid_str: return int(vid_str) + except: + return -1 + + if '.' in ifaceobj.name: + vid_str = ifaceobj.name.split('.', 1)[1] + elif ifaceobj.name.startswith('vlan'): + vid_str = ifaceobj.name[4:] + else: + return -1 + try: + vid = int(vid_str) + except: + return -1 + return vid + + def _is_vlan_by_name(self, ifacename): + return '.' in ifacename + + def _get_vlan_raw_device_from_ifacename(self, ifacename): + """ Returns vlan raw device from ifname + Example: + Returns eth0 for ifname eth0.100 + + Returns None if vlan raw device name cannot + be determined + """ + vlist = ifacename.split('.', 1) + if len(vlist) == 2: + return vlist[0] + return None + + def _get_vlan_raw_device(self, ifaceobj): + vlan_raw_device = ifaceobj.get_attr_value_first('vlan-raw-device') + if vlan_raw_device: + return vlan_raw_device + return self._get_vlan_raw_device_from_ifacename(ifaceobj.name) + + def get_dependent_ifacenames(self, ifaceobj, ifaceobjs_all=None): + if not self._is_vlan_device(ifaceobj): + return None + return [self._get_vlan_raw_device(ifaceobj)] + + def _bridge_vid_add_del(self, ifaceobj, bridgename, vlanid, + add=True): + """ If the lower device is a vlan aware bridge, add/del the vlanid + to the bridge """ + if self.ipcmd.bridge_is_vlan_aware(bridgename): + if add: + rtnetlink_api.rtnl_api.bridge_vlan(add=True, dev=bridgename, + vid=vlanid, master=False) + else: + rtnetlink_api.rtnl_api.bridge_vlan(add=False, dev=bridgename, + vid=vlanid, master=False) + + def _bridge_vid_check(self, ifaceobj, ifaceobjcurr, bridgename, vlanid): + """ If the lower device is a vlan aware bridge, check if the vlanid + is configured on the bridge """ + if not self.ipcmd.bridge_is_vlan_aware(bridgename): + return + vids = self._bridge_vids_query_cache.get(bridgename) + if vids == None: + vids = self.ipcmd.bridge_port_vids_get(bridgename) + self._bridge_vids_query_cache[bridgename] = vids + if not vids or vlanid not in vids: + ifaceobjcurr.status = ifaceStatus.ERROR + ifaceobjcurr.status_str = 'bridge vid error' + + def _up(self, ifaceobj): + vlanid = self._get_vlan_id(ifaceobj) + if vlanid == -1: + raise Exception('could not determine vlanid') + if self._handle_reserved_vlan(vlanid, ifaceobj.name): + return + vlanrawdevice = self._get_vlan_raw_device(ifaceobj) + if not vlanrawdevice: + raise Exception('could not determine vlan raw device') + if not self.PERFMODE: + if not self.ipcmd.link_exists(vlanrawdevice): + raise Exception('rawdevice %s not present' %vlanrawdevice) + if self.ipcmd.link_exists(ifaceobj.name): + self._bridge_vid_add_del(ifaceobj, vlanrawdevice, vlanid) + return + rtnetlink_api.rtnl_api.create_vlan(vlanrawdevice, + ifaceobj.name, vlanid) + self._bridge_vid_add_del(ifaceobj, vlanrawdevice, vlanid) + if ifaceobj.addr_method == 'manual': + rtnetlink_api.rtnl_api.link_set(ifaceobj.name, "up") + + def _down(self, ifaceobj): + vlanid = self._get_vlan_id(ifaceobj) + if vlanid == -1: + raise Exception('could not determine vlanid') + vlanrawdevice = self._get_vlan_raw_device(ifaceobj) + if not vlanrawdevice: + raise Exception('could not determine vlan raw device') + if not self.PERFMODE and not self.ipcmd.link_exists(ifaceobj.name): + return + try: + self.ipcmd.link_delete(ifaceobj.name) + self._bridge_vid_add_del(ifaceobj, vlanrawdevice, vlanid, add=False) + except Exception, e: + self.log_warn(str(e)) + + def _query_check(self, ifaceobj, ifaceobjcurr): + if not self.ipcmd.link_exists(ifaceobj.name): + return + if not '.' in ifaceobj.name: + # if vlan name is not in the dot format, check its running state + (vlanrawdev, vlanid) = self.ipcmd.get_vlandev_attrs(ifaceobj.name) + if vlanrawdev != ifaceobj.get_attr_value_first('vlan-raw-device'): + ifaceobjcurr.update_config_with_status('vlan-raw-device', + vlanrawdev, 1) + else: + ifaceobjcurr.update_config_with_status('vlan-raw-device', + vlanrawdev, 0) + if vlanid != ifaceobj.get_attr_value_first('vlan-id'): + ifaceobjcurr.update_config_with_status('vlan-id', vlanid, 1) + else: + ifaceobjcurr.update_config_with_status('vlan-id', + vlanid, 0) + self._bridge_vid_check(ifaceobj, ifaceobjcurr, vlanrawdev, vlanid) + + def _query_running(self, ifaceobjrunning): + if not self.ipcmd.link_exists(ifaceobjrunning.name): + return + if not self.ipcmd.get_vlandev_attrs(ifaceobjrunning.name): + return + # If vlan name is not in the dot format, get the + # vlan dev and vlan id + if not '.' in ifaceobjrunning.name: + (vlanrawdev, vlanid) = self.ipcmd.get_vlandev_attrs(ifaceobjrunning.name) + ifaceobjrunning.update_config_dict({(k, v) for k, v in + {'vlan-raw-device' : vlanrawdev, + 'vlan-id' : vlanid}.items() + if v}) + + _run_ops = {'pre-up' : _up, + 'post-down' : _down, + 'query-checkcurr' : _query_check, + 'query-running' : _query_running} + + def get_ops(self): + """ returns list of ops supported by this module """ + return self._run_ops.keys() + + def _init_command_handlers(self): + if not self.ipcmd: + self.ipcmd = iproute2(**self.get_flags()) + + + def run(self, ifaceobj, operation, query_ifaceobj=None, **extra_args): + """ run vlan configuration on the interface object passed as argument + + Args: + **ifaceobj** (object): iface object + + **operation** (str): any of 'pre-up', 'post-down', 'query-checkcurr', + 'query-running' + Kwargs: + **query_ifaceobj** (object): query check ifaceobject. This is only + valid when op is 'query-checkcurr'. It is an object same as + ifaceobj, but contains running attribute values and its config + status. The modules can use it to return queried running state + of interfaces. status is success if the running state is same + as user required state in ifaceobj. error otherwise. + """ + if ifaceobj.type == ifaceType.BRIDGE_VLAN: + return + op_handler = self._run_ops.get(operation) + if not op_handler: + return + if (operation != 'query-running' and + not self._is_vlan_device(ifaceobj)): + return + self._init_command_handlers() + if operation == 'query-checkcurr': + op_handler(self, ifaceobj, query_ifaceobj) + else: + op_handler(self, ifaceobj) diff --git a/ifupdown2/addons/vrrpd.py b/ifupdown2/addons/vrrpd.py new file mode 100644 index 0000000..9791141 --- /dev/null +++ b/ifupdown2/addons/vrrpd.py @@ -0,0 +1,167 @@ +#!/usr/bin/python +# +# Copyright 2014 Cumulus Networks, Inc. All rights reserved. +# Author: Roopa Prabhu, roopa@cumulusnetworks.com +# + +try: + from ipaddr import IPNetwork + from sets import Set + from ifupdown.iface import * + from ifupdownaddons.modulebase import moduleBase + from ifupdownaddons.iproute2 import iproute2 + import os + import glob + import logging + import signal + import subprocess + import re +except ImportError, e: + raise ImportError (str(e) + "- required module not found") + +class vrrpd(moduleBase): + """ ifupdown2 addon module to configure vrrpd attributes """ + + _modinfo = {'mhelp' : 'ethtool configuration module for interfaces', + 'attrs': { + 'vrrp-id' : + {'help' : 'vrrp instance id', + 'example' : ['vrrp-id 1']}, + 'vrrp-priority' : + {'help': 'set vrrp priority', + 'example' : ['vrrp-priority 20']}, + 'vrrp-virtual-ip' : + {'help': 'set vrrp virtual ip', + 'example' : ['vrrp-virtual-ip 10.0.1.254']}}} + + def __init__(self, *args, **kargs): + moduleBase.__init__(self, *args, **kargs) + self.ipcmd = None + + def _check_if_process_is_running(self, cmdname, cmdline): + targetpids = [] + pidstr = '' + try: + pidstr = subprocess.check_output(['/bin/pidof', + '%s' %cmdname]).strip('\n') + except: + pass + if not pidstr: + return [] + + pids = pidstr.split() + if not pids: + return targetpids + for pid in pids: + tmpcmdline = cmdline.replace(' ', '') + try: + pcmdline = self.read_file_oneline('/proc/%s/cmdline' %pid) + pcmdline = re.sub(r'\\(.)', r'\1', pcmdline) + self.logger.info('(%s)' %(pcmdline)) + self.logger.info('(%s)' %(tmpcmdline)) + self.logger.info('(%d) (%d)' %(len(pcmdline), len(tmpcmdline))) + if pcmdline and pcmdline == tmpcmdline: + targetpids.append(pid) + except: + pass + return targetpids + + def _up(self, ifaceobj): + """ up vrrpd -n -D -i $IFACE -v 1 -p 20 10.0.1.254 + up ifplugd -i $IFACE -b -f -u0 -d1 -I -p -q """ + + if (not self.DRYRUN and + not os.path.exists('/sys/class/net/%s' %ifaceobj.name)): + return + + cmd = '' + attrval = ifaceobj.get_attr_value_first('vrrp-id') + if attrval: + cmd += ' -v %s' %attrval + else: + return + attrval = ifaceobj.get_attr_value_first('vrrp-priority') + if attrval: + cmd += ' -p %s' %attrval + else: + self.logger.warn('%s: incomplete vrrp parameters ' %ifaceobj.name, + '(priority not found)') + attrval = ifaceobj.get_attr_value_first('vrrp-virtual-ip') + if attrval: + cmd += ' %s' %attrval + else: + self.logger.warn('%s: incomplete vrrp arguments ' %ifaceobj.name, + '(virtual ip not found)') + return + cmd = '/usr/sbin/vrrpd -n -D -i %s %s' %(ifaceobj.name, cmd) + self.exec_command(cmd) + + cmd = '/usr/sbin/ifplugd -i %s -b -f -u0 -d1 -I -p -q' %ifaceobj.name + if self._check_if_process_is_running('/usr/sbin/ifplugd', cmd): + self.logger.info('%s: ifplugd already running' %ifaceobj.name) + return + self.exec_command(cmd) + + def _kill_pid_from_file(self, pidfilename): + if os.path.exists(pidfilename): + pid = self.read_file_oneline(pidfilename) + if os.path.exists('/proc/%s' %pid): + os.kill(int(pid), signal.SIGTERM) + + def _down(self, ifaceobj): + """ down ifplugd -k -i $IFACE + down kill $(cat /var/run/vrrpd_$IFACE_*.pid) """ + attrval = ifaceobj.get_attr_value_first('vrrp-id') + if not attrval: + return + try: + self.exec_command('/usr/sbin/ifplugd -k -i %s' %ifaceobj.name) + except Exception, e: + self.logger.debug('%s: ifplugd down error (%s)' + %(ifaceobj.name, str(e))) + pass + + for pidfile in glob.glob('/var/run/vrrpd_%s_*.pid' %ifaceobj.name): + try: + self._kill_pid_from_file(pidfile) + except Exception, e: + self.logger.debug('%s: vrrpd down error (%s)' + %(ifaceobj.name, str(e))) + pass + + def _query_check(self, ifaceobj, ifaceobjcurr): + # XXX + return + + + _run_ops = {'post-up' : _up, + 'pre-down' : _down} + + def get_ops(self): + """ returns list of ops supported by this module """ + return self._run_ops.keys() + + def run(self, ifaceobj, operation, query_ifaceobj=None, **extra_args): + """ run ethtool configuration on the interface object passed as + argument + + Args: + **ifaceobj** (object): iface object + + **operation** (str): any of 'post-up', 'query-checkcurr', + 'query-running' + Kwargs: + **query_ifaceobj** (object): query check ifaceobject. This is only + valid when op is 'query-checkcurr'. It is an object same as + ifaceobj, but contains running attribute values and its config + status. The modules can use it to return queried running state + of interfaces. status is success if the running state is same + as user required state in ifaceobj. error otherwise. + """ + op_handler = self._run_ops.get(operation) + if not op_handler: + return + if operation == 'query-checkcurr': + op_handler(self, ifaceobj, query_ifaceobj) + else: + op_handler(self, ifaceobj) diff --git a/ifupdown2/addons/vxlan.py b/ifupdown2/addons/vxlan.py new file mode 100644 index 0000000..a3a3fbc --- /dev/null +++ b/ifupdown2/addons/vxlan.py @@ -0,0 +1,158 @@ +#!/usr/bin/python + +from ifupdown.iface import * +from ifupdownaddons.modulebase import moduleBase +from ifupdownaddons.iproute2 import iproute2 +import ifupdown.rtnetlink_api as rtnetlink_api +import logging +from sets import Set + +class vxlan(moduleBase): + _modinfo = {'mhelp' : 'vxlan module configures vxlan interfaces.', + 'attrs' : { + 'vxlan-id' : + {'help' : 'vxlan id', + 'required' : True, + 'example': ['vxlan-id 100']}, + 'vxlan-local-tunnelip' : + {'help' : 'vxlan local tunnel ip', + 'example': ['vxlan-local-tunnelip 172.16.20.103']}, + 'vxlan-svcnodeip' : + {'help' : 'vxlan id', + 'example': ['vxlan-svcnodeip 172.16.22.125']}, + 'vxlan-peernodeip' : + {'help' : 'vxlan peer node ip', + 'example': ['vxlan-peernodeip 172.16.22.127']}, + 'vxlan-learning' : + {'help' : 'vxlan learning on/off', + 'example': ['vxlan-learning off'], + 'default': 'on'}, + }} + + def __init__(self, *args, **kargs): + moduleBase.__init__(self, *args, **kargs) + self.ipcmd = None + + def _is_vxlan_device(self, ifaceobj): + if ifaceobj.get_attr_value_first('vxlan-id'): + return True + return False + + def _up(self, ifaceobj): + vxlanid = ifaceobj.get_attr_value_first('vxlan-id') + if vxlanid: + self.ipcmd.link_create_vxlan(ifaceobj.name, vxlanid, + localtunnelip=ifaceobj.get_attr_value_first('vxlan-local-tunnelip'), + svcnodeips=ifaceobj.get_attr_value('vxlan-svcnodeip'), + peernodeips=ifaceobj.get_attr_value('vxlan-peernodeip'), + learning=ifaceobj.get_attr_value_first('vxlan-learning')) + if ifaceobj.addr_method == 'manual': + rtnetlink_api.rtnl_api.link_set(ifaceobj.name, "up") + + def _down(self, ifaceobj): + try: + self.ipcmd.link_delete(ifaceobj.name) + except Exception, e: + self.log_warn(str(e)) + + def _query_check_n_update(self, ifaceobjcurr, attrname, attrval, + running_attrval): + if running_attrval and attrval == running_attrval: + ifaceobjcurr.update_config_with_status(attrname, attrval, 0) + else: + ifaceobjcurr.update_config_with_status(attrname, running_attrval, 1) + + def _query_check_n_update_addresses(self, ifaceobjcurr, attrname, + addresses, running_addresses): + if addresses: + for a in addresses: + if a in running_addresses: + ifaceobjcurr.update_config_with_status(attrname, a, 0) + else: + ifaceobjcurr.update_config_with_status(attrname, a, 1) + running_addresses = Set(running_addresses).difference( + Set(addresses)) + [ifaceobjcurr.update_config_with_status(attrname, a, 1) + for a in running_addresses] + + def _query_check(self, ifaceobj, ifaceobjcurr): + if not self.ipcmd.link_exists(ifaceobj.name): + return + # Update vxlan object + vxlanattrs = self.ipcmd.get_vxlandev_attrs(ifaceobj.name) + if not vxlanattrs: + ifaceobjcurr.check_n_update_config_with_status_many(ifaceobj, + self.get_mod_attrs(), -1) + return + self._query_check_n_update(ifaceobjcurr, 'vxlan-id', + ifaceobj.get_attr_value_first('vxlan-id'), + vxlanattrs.get('vxlanid')) + + self._query_check_n_update(ifaceobjcurr, 'vxlan-local-tunnelip', + ifaceobj.get_attr_value_first('vxlan-local-tunnelip'), + vxlanattrs.get('local')) + + self._query_check_n_update_addresses(ifaceobjcurr, 'vxlan-svcnodeip', + ifaceobj.get_attr_value('vxlan-svcnodeip'), + vxlanattrs.get('svcnode', [])) + + self._query_check_n_update_addresses(ifaceobjcurr, 'vxlan-peernodeip', + ifaceobj.get_attr_value('vxlan-peernodeip'), + vxlanattrs.get('peernode', [])) + + learning = ifaceobj.get_attr_value_first('vxlan-learning') + running_learning = vxlanattrs.get('learning') + if learning == running_learning: + ifaceobjcurr.update_config_with_status('vxlan-learning', + running_learning, 0) + else: + ifaceobjcurr.update_config_with_status('vxlan-learning', + running_learning, 1) + + def _query_running(self, ifaceobjrunning): + vxlanattrs = self.ipcmd.get_vxlandev_attrs(ifaceobjrunning.name) + if not vxlanattrs: + return + attrval = vxlanattrs.get('vxlanid') + if attrval: + ifaceobjrunning.update_config('vxlan-id', vxlanattrs.get('vxlanid')) + attrval = vxlanattrs.get('local') + if attrval: + ifaceobjrunning.update_config('vxlan-local-tunnelip', attrval) + attrval = vxlanattrs.get('svcnode') + if attrval: + [ifaceobjrunning.update_config('vxlan-svcnode', a) + for a in attrval] + attrval = vxlanattrs.get('peernode') + if attrval: + [ifaceobjrunning.update_config('vxlan-peernode', a) + for a in attrval] + attrval = vxlanattrs.get('learning') + if attrval and attrval == 'on': + ifaceobjrunning.update_config('vxlan-learning', 'on') + + + _run_ops = {'pre-up' : _up, + 'post-down' : _down, + 'query-checkcurr' : _query_check, + 'query-running' : _query_running} + + def get_ops(self): + return self._run_ops.keys() + + def _init_command_handlers(self): + if not self.ipcmd: + self.ipcmd = iproute2(**self.get_flags()) + + def run(self, ifaceobj, operation, query_ifaceobj=None, **extra_args): + op_handler = self._run_ops.get(operation) + if not op_handler: + return + if (operation != 'query-running' and + not self._is_vxlan_device(ifaceobj)): + return + self._init_command_handlers() + if operation == 'query-checkcurr': + op_handler(self, ifaceobj, query_ifaceobj) + else: + op_handler(self, ifaceobj) diff --git a/ifupdown2/config/addons.conf b/ifupdown2/config/addons.conf new file mode 100644 index 0000000..e9aa487 --- /dev/null +++ b/ifupdown2/config/addons.conf @@ -0,0 +1,28 @@ +pre-up,ifenslave +pre-up,clagd +pre-up,vlan +pre-up,vxlan +pre-up,usercmds +pre-up,bridge +pre-up,bridgevlan +pre-up,mstpctl +up,dhcp +up,address +up,addressvirtual +up,usercmds +post-up,ethtool +post-up,usercmds +post-up,clagd +pre-down,usercmds +down,dhcp +down,addressvirtual +down,address +down,usercmds +post-down,clagd +post-down,mstpctl +post-down,bridgevlan +post-down,bridge +post-down,vxlan +post-down,vlan +post-down,ifenslave +post-down,usercmds diff --git a/ifupdown2/config/ifupdown2.conf b/ifupdown2/config/ifupdown2.conf index bcd1401..8186e48 100644 --- a/ifupdown2/config/ifupdown2.conf +++ b/ifupdown2/config/ifupdown2.conf @@ -9,3 +9,32 @@ template_engine=mako # default template lookup path during template rendering template_lookuppath=/etc/network/ifupdown2/templates + +# Support /etc/network/if-*/ scripts +addon_scripts_support=0 + +# By default ifupdown2 only supports a single vlan filtering bridge +# on the system. Set this flag to 1 to support multiple vlan +# filtering bridges +multiple_vlan_aware_bridge_support=0 + +# ifquery check status strings. +# By default `ifquery --check` prints the check and +# cross marks against interface attributes. +# Use the below strings to modify the default behaviour. +# +ifquery_check_success_str=[pass] +ifquery_check_error_str=[fail] +ifquery_check_unknown_str= +# + +# This attribute controls iface/vlan range expansions +# in ifquery default output. +ifquery_ifacename_expand_range=0 + +# Let link master (bridges, bonds) own the link state of slaves +link_master_slave=1 + +# Delay admin state change till the end +delay_admin_state_change=0 + diff --git a/ifupdown2/config/networking b/ifupdown2/config/networking new file mode 100644 index 0000000..8b0474c --- /dev/null +++ b/ifupdown2/config/networking @@ -0,0 +1,17 @@ +# +# +# Parameters for the /etc/init.d/networking script +# +# + +# Change the below to yes if you want verbose logging to be enabled +VERBOSE="no" + +# Change the below to yes if you want debug logging to be enabled +DEBUG="no" + +# Change the below to yes if you want logging to go to syslog +SYSLOG="no" + +# Exclude interfaces +EXCLUDE_INTERFACES= diff --git a/ifupdown2/debian/python-ifupdown2.postinst b/ifupdown2/debian/python-ifupdown2.postinst index 9247975..10ca49a 100644 --- a/ifupdown2/debian/python-ifupdown2.postinst +++ b/ifupdown2/debian/python-ifupdown2.postinst @@ -50,10 +50,10 @@ case "$1" in fi fi - [ -e /sbin/ifup ] || ln -s /sbin/ifupdown /sbin/ifup - [ -e /sbin/ifdown ] || ln -s /sbin/ifupdown /sbin/ifdown - [ -e /sbin/ifquery ] || ln -s /sbin/ifupdown /sbin/ifquery - [ -e /sbin/ifreload ] || ln -s /sbin/ifupdown /sbin/ifreload + [ -e /sbin/ifup ] || ln -sf /sbin/ifupdown /sbin/ifup + [ -e /sbin/ifdown ] || ln -sf /sbin/ifupdown /sbin/ifdown + [ -e /sbin/ifquery ] || ln -sf /sbin/ifupdown /sbin/ifquery + [ -e /sbin/ifreload ] || ln -sf /sbin/ifupdown /sbin/ifreload (cd /usr/share/man/man8/ && ln -sf /usr/share/man/man8/ifup.8.gz ifdown.8.gz) diff --git a/ifupdown2/docs.addons/Makefile b/ifupdown2/docs.addons/Makefile new file mode 100644 index 0000000..9b5efd7 --- /dev/null +++ b/ifupdown2/docs.addons/Makefile @@ -0,0 +1,153 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = build + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +clean: + -rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/ifupdown2.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/ifupdown2.qhc" + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/ifupdown2" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/ifupdown2" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." diff --git a/ifupdown2/docs.addons/source/addonsapiref.rst b/ifupdown2/docs.addons/source/addonsapiref.rst new file mode 100644 index 0000000..0954d27 --- /dev/null +++ b/ifupdown2/docs.addons/source/addonsapiref.rst @@ -0,0 +1,61 @@ +Documentation for the ifupdownaddons default addons modules +*********************************************************** + +address +======= + +.. automodule:: address + +.. autoclass:: address + :members: run, get_ops + + +bridge +====== + +.. automodule:: bridge + +.. autoclass:: bridge + :members: run, get_ops + +dhcp +==== + +.. automodule:: dhcp + +.. autoclass:: dhcp + +ethtool +======= + +.. automodule:: ethtool + +.. autoclass:: ethtool + +ifenslave +========= + +.. automodule:: ifenslave + +.. autoclass:: ifenslave + +mstpctl +======= + +.. automodule:: mstpctl + +.. autoclass:: mstpctl + +usercmds +======== + +.. automodule:: usercmds + +.. autoclass:: usercmds + +vlan +==== + +.. automodule:: vlan + +.. autoclass:: vlan diff --git a/ifupdown2/docs.addons/source/addonshelperapiref.rst b/ifupdown2/docs.addons/source/addonshelperapiref.rst new file mode 100644 index 0000000..01d9a41 --- /dev/null +++ b/ifupdown2/docs.addons/source/addonshelperapiref.rst @@ -0,0 +1,44 @@ +Documentation for the ifupdownaddons package helper modules +*********************************************************** + +This package contains modules that provide helper methods +for ifupdown2 addon modules to interact directly with tools +like iproute2, brctl etc. + + +bridgeutils +=========== + +Helper module to work with bridgeutil commands + +.. automodule:: bridgeutils + +.. autoclass:: brctl + +ifenslaveutil +============= + +Helper module to interact with linux api to create bonds. +Currently this is via sysfs. + +.. automodule:: ifenslaveutil + +.. autoclass:: ifenslaveutil + +dhclient +======== + +Helper module to interact with dhclient tools. + +.. automodule:: dhclient + +.. autoclass:: dhclient + +iproute2 +======== + +Helper module to interact with iproute2 tools. + +.. automodule:: iproute2 + +.. autoclass:: iproute2 diff --git a/ifupdown2/docs.addons/source/conf.py b/ifupdown2/docs.addons/source/conf.py new file mode 100644 index 0000000..e9ddb8d --- /dev/null +++ b/ifupdown2/docs.addons/source/conf.py @@ -0,0 +1,247 @@ +# -*- coding: utf-8 -*- +# +# ifupdown2-addons documentation build configuration file, created by +# sphinx-quickstart on Mon Jul 21 11:17:17 2014. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys, os + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +#sys.path.insert(0, os.path.abspath('.')) + +sys.path.insert(0, os.path.abspath('../../addons')) +sys.path.append(os.path.abspath('../../')) +sys.path.append(os.path.abspath('../../ifupdownaddons')) +sys.path.append(os.path.abspath('../../../ifupdown2')) + +# -- General configuration ----------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be extensions +# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = ['sphinx.ext.autodoc'] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'ifupdown2-addons' +copyright = u'2014, Roopa Prabhu' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '0.1' +# The full version, including alpha/beta/rc tags. +release = '0.1' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = [] + +# The reST default role (used for this markup: `text`) to use for all documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + + +# -- Options for HTML output --------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'default' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Output file base name for HTML help builder. +htmlhelp_basename = 'ifupdown2-addonsdoc' + + +# -- Options for LaTeX output -------------------------------------------------- + +latex_elements = { +# The paper size ('letterpaper' or 'a4paper'). +#'papersize': 'letterpaper', + +# The font size ('10pt', '11pt' or '12pt'). +#'pointsize': '10pt', + +# Additional stuff for the LaTeX preamble. +#'preamble': '', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass [howto/manual]). +latex_documents = [ + ('index', 'ifupdown2-addons.tex', u'ifupdown2-addons Documentation', + u'Roopa Prabhu', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output -------------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('index', 'ifupdown2-addons', u'ifupdown2-addons Documentation', + [u'Roopa Prabhu'], 1) +] + +# If true, show URL addresses after external links. +#man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------------ + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ('index', 'ifupdown2-addons', u'ifupdown2-addons Documentation', + u'Roopa Prabhu', 'ifupdown2-addons', 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +#texinfo_appendices = [] + +# If false, no module index is generated. +#texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#texinfo_show_urls = 'footnote' diff --git a/ifupdown2/docs.addons/source/developmentcorner.rst b/ifupdown2/docs.addons/source/developmentcorner.rst new file mode 100644 index 0000000..be5c563 --- /dev/null +++ b/ifupdown2/docs.addons/source/developmentcorner.rst @@ -0,0 +1,58 @@ +Development Corner +================== + +Writing a ifupdown2 addon module +-------------------------------- +ifupdown2 addon modules are part of the python-ifupdown2-addons package. +They are installed under /usr/share/ifupdownaddons directory on the target +system. + +The center of the universe for an addon module is the 'class iface' object +exported by the python-ifupdown2 package. + +The iface object is modeled after an iface entry in the user provided network +configuration file (eg. /etc/network/interfaces). For more details see +the api reference for the iface class. + +ifupdown2 dynamically loads a python addon module. It expects the addon module +to implement a few methods. + +* all addon modules must inherit from moduleBase class +* the module must implement a class by the same name +* the network interface object (class iface) and the operation to be performed + is passed to the modules. Operation can be any of 'pre-up', 'up', 'post-up', + 'pre-down', 'down', 'post-down', 'query-check', 'query-running'. + The module can choose to support a subset or all operations. + In cases when the operation is query-check, the module must compare between + the given and running state and return the checked state of the object in + queryobjcur passed as argument to the run menthod. +* the python addon class must provide a few methods: + * run() : method to configure the interface. + * get_ops() : must return a list of operations it supports. + eg: 'pre-up', 'post-down' + * get_dependent_ifacenames() : must return a list of interfaces the + supported interface is dependent on. This is used to build the + dependency list for sorting and executing interfaces in dependency order. + * if the module supports -r option to ifquery, ie ability to construct the + ifaceobj from running state, it can optionally implement the + get_dependent_ifacenames_running() method, to return the list of + dependent interfaces derived from running state of the interface. + This is different from get_dependent_ifacenames() where the dependent + interfaces are derived from the interfaces config file (provided by the + user). + * provide a dictionary of all supported attributes in the _modinfo + attribute. This is useful for syntax help and man page generation. + +python-ifupdown2-addons package also installs ifupdownaddons python package +that contains helper modules for all addon modules. Its optional for the addon +module to use this package. + +see example address handling module /usr/share/ifupdownaddons/address.py + +API reference +------------- +.. toctree:: + :maxdepth: 2 + + addonsapiref.rst + addonshelperapiref.rst diff --git a/ifupdown2/docs.addons/source/gettingstarted.rst b/ifupdown2/docs.addons/source/gettingstarted.rst new file mode 100644 index 0000000..e4345ca --- /dev/null +++ b/ifupdown2/docs.addons/source/gettingstarted.rst @@ -0,0 +1,29 @@ +Getting Started +=============== + +Prerequisites +------------- +* python-ifupdown2-addons is currently only tested on debian wheezy +* python-ifupdown2-addons needs python version 2.6 or greater +* build depends on: python-stdeb (for deb builds), python-docutils (for rst2man) +* depends on python-gvgen package for printing interface graphs (this will be made optional in the future) +* optional dependency for template engine: python-mako +* python-ifupdown2-addons has an install dependency on python-ifupdown2 + +Building +-------- +$git clone ifupdown2 + +$cd ifupdown2/ifupdown2-addons + +$./build.sh + +Installing +---------- +install generated python-ifupdown2-addons-.deb using dpkg + +$dpkg -i .deb + + + + diff --git a/ifupdown2/docs.addons/source/index.rst b/ifupdown2/docs.addons/source/index.rst new file mode 100644 index 0000000..27b6fd6 --- /dev/null +++ b/ifupdown2/docs.addons/source/index.rst @@ -0,0 +1,25 @@ +.. ifupdown2 documentation master file, created by + sphinx-quickstart on Sun Jul 6 23:49:20 2014. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to ifupdown2-addons documentation! +========================================== + +Contents: +========= + +.. toctree:: + :maxdepth: 2 + + intro.rst + gettingstarted.rst + developmentcorner.rst + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + diff --git a/ifupdown2/docs.addons/source/intro.rst b/ifupdown2/docs.addons/source/intro.rst new file mode 100644 index 0000000..35f187e --- /dev/null +++ b/ifupdown2/docs.addons/source/intro.rst @@ -0,0 +1,21 @@ +python-ifupdown2-addons +----------------------- + +The python-ifupdown2-addons package contains ifupdown2 addon modules. + +addon modules are responsible for applying interface configuration. +The modules are installed under /usr/share/ifupdownmodules. + +Each module can declare its own set of supported attributes. Each module +is passed the iface object (which is a representation of /etc/network/interfaces +iface entry). Each module is also passed the operation to be performed. + +Example modules are /usr/share/ifupdownmodules/address.py, +/usr/share/ifupdownmodules/bridge.py etc + +The order in which these modules are invoked is listed in +/var/lib/ifupdownaddons/addons.conf. There is a ifaddon utility in the works +to better manage the module ordering. + +For details on how to add a module, see the api reference and development +documentation. diff --git a/ifupdown2/docs/examples/interfaces_bridge_igmp_mstp b/ifupdown2/docs/examples/interfaces_bridge_igmp_mstp index 5efb99c..f4916f4 100644 --- a/ifupdown2/docs/examples/interfaces_bridge_igmp_mstp +++ b/ifupdown2/docs/examples/interfaces_bridge_igmp_mstp @@ -6,6 +6,9 @@ # Except bridge-ports, none of the other attributes are required. Default # values are documented in the ifupdown-addons-interfaces(5) man page. # +# The bridge in the example below is a vlan unaware bridge (classic linux +# bridge) +# auto br-300 iface br-300 inet static address 12.0.0.3/24 diff --git a/ifupdown2/docs/examples/vlan_aware_bridges/interfaces.basic b/ifupdown2/docs/examples/vlan_aware_bridges/interfaces.basic new file mode 100644 index 0000000..7506175 --- /dev/null +++ b/ifupdown2/docs/examples/vlan_aware_bridges/interfaces.basic @@ -0,0 +1,25 @@ +# +# vlan-aware bridge simple example +# +# 'bridge' is a vlan aware bridge with all ports (swp1-52). +# native vlan is by default 1 +# +# 'bridge-vids' attribute is used to declare vlans. +# 'bridge-pvid' attribute is used to specify native vlans if other than 1 +# 'bridge-access' attribute is used to declare access port +# + +# +# ports swp1-swp52 are trunk ports which inherit vlans from 'bridge' +# ie vlans 310 700 707 712 850 910 + +# +# the following is a vlan aware bridge with ports swp1-swp52 +# It has stp on +# +auto bridge +iface bridge + bridge-vlan-aware yes + bridge-ports glob swp1-52 + bridge-stp on + bridge-vids 310 700 707 712 850 910 diff --git a/ifupdown2/docs/examples/vlan_aware_bridges/interfaces.vlan_prune_and_access_ports b/ifupdown2/docs/examples/vlan_aware_bridges/interfaces.vlan_prune_and_access_ports new file mode 100644 index 0000000..9641a82 --- /dev/null +++ b/ifupdown2/docs/examples/vlan_aware_bridges/interfaces.vlan_prune_and_access_ports @@ -0,0 +1,59 @@ +# +# vlan-aware bridge access ports and pruned vlan example +# +# 'bridge' is a vlan aware bridge with all ports (swp1-52). +# native vlan is by default 1 +# +# 'bridge-vids' attribute is used to declare vlans. +# 'bridge-pvid' attribute is used to specify native vlans if other than 1 +# 'bridge-access' attribute is used to declare access port +# +# + +# The following is an access port to vlan 310, no trunking +auto swp1 +iface swp1 + bridge-access 310 + mstpctl-portadminedge yes + mstpctl-bpduguard yes + +# The following is a truk port that is "pruned". +# native vlan is 1, but only .1q tags of 707, 712, 850 are +# sent and received +# +auto swp2 +iface swp2 + bridge-vids 707 712 850 + mstpctl-portadminedge yes + mstpctl-bpduguard yes + +# The following port is the trunk uplink and inherits all vlans +# from 'bridge' +auto swp49 +iface swp49 + mstpctl-portpathcost 10 + # Enable bridge assurance on uplink port using 'portnetwork' attribute + mstpctl-portnetwork yes + +# The following port is the trunk uplink and inherits all vlans +# from 'bridge' +auto swp50 +iface swp50 + mstpctl-portpathcost 0 + # Enable bridge assurance on uplink port using 'portnetwork' attribute + mstpctl-portnetwork yes + +# +# ports swp3-swp48 are trunk ports which inherit vlans from the 'bridge' +# ie vlans 310,700,707,712,850,910 + +# +# the following is a vlan aware bridge with ports swp1-swp52 +# It has stp on +# +auto bridge +iface bridge + bridge-vlan-aware yes + bridge-ports glob swp1-52 + bridge-stp on + bridge-vids 310 700 707 712 850 910 diff --git a/ifupdown2/docs/examples/vlan_aware_bridges/interfaces.with_bonds b/ifupdown2/docs/examples/vlan_aware_bridges/interfaces.with_bonds new file mode 100644 index 0000000..f3425dd --- /dev/null +++ b/ifupdown2/docs/examples/vlan_aware_bridges/interfaces.with_bonds @@ -0,0 +1,92 @@ +# +# vlan-aware bridge with bonds example +# +# uplink1, peerlink and downlink are bond interfaces. +# 'bridge' is a vlan aware bridge with ports uplink1, peerlink +# and downlink (swp2-20). +# +# native vlan is by default 1 +# +# 'bridge-vids' attribute is used to declare vlans. +# 'bridge-pvid' attribute is used to specify native vlans if other than 1 +# 'bridge-access' attribute is used to declare access port +# +auto lo +iface lo + +auto eth0 +iface eth0 inet dhcp + +# bond interface +auto uplink1 +iface uplink1 + bond-slaves swp32 + bond-mode 802.3ad + bond-miimon 100 + bond-use-carrier 1 + bond-lacp-rate 1 + bond-min-links 1 + bond-xmit-hash-policy layer2 + bridge-vids 2000-2079 + +# bond interface +auto peerlink +iface peerlink + bond-slaves swp30 swp31 + bond-mode 802.3ad + bond-miimon 100 + bond-use-carrier 1 + bond-lacp-rate 1 + bond-min-links 1 + bond-xmit-hash-policy layer3+4 + bridge-vids 2000-2079 4094 + +# bond interface +auto downlink +iface downlink + bond-slaves swp1 + bond-mode 802.3ad + bond-miimon 100 + bond-use-carrier 1 + bond-lacp-rate 1 + bond-min-links 1 + bond-xmit-hash-policy layer3+4 + bridge-vids 2000-2079 + +# +# Declare vlans for all swp ports +# swp2-20 get vlans from 2004 to 2022. +# The below uses mako templates to generate iface sections +# with vlans for swp ports +# +%for port, vlanid in zip(range(2, 20), range(2004, 2022)) : + auto swp${port} + iface swp${port} + bridge-vids ${vlanid} + +%endfor + +# svi vlan 4094 +auto bridge.4094 +iface bridge.4094 + address 11.100.1.252/24 + +# l2 attributes for vlan 4094 +auto bridge.4094 +vlan bridge.4094 + bridge-igmp-querier-src 172.16.101.1 + +# +# vlan aware bridge +# +auto bridge +iface bridge + bridge-vlan-aware yes + bridge-ports uplink1 peerlink downlink glob swp2-20 + bridge-stp on + +# svi peerlink vlan +auto peerlink.4094 +iface peerlink.4094 + address 192.168.10.1/30 + broadcast 192.168.10.3 diff --git a/ifupdown2/docs/examples/vlan_aware_bridges/interfaces.with_clag b/ifupdown2/docs/examples/vlan_aware_bridges/interfaces.with_clag new file mode 100644 index 0000000..8cdccc9 --- /dev/null +++ b/ifupdown2/docs/examples/vlan_aware_bridges/interfaces.with_clag @@ -0,0 +1,87 @@ +# +# vlan-aware bridge with clag example +# +# +# 'bridge' is a vlan aware bridge with ports: +# 'peer-bond spine-bond glob host-bond-0[1-2]' +# +# All ports inherit 'vlans 10 20-23' from the 'bridge-vids' attribute +# under the bridge +# +# native vlan is by default 1 +# +# 'bridge-vids' attribute is used to declare vlans. +# 'bridge-pvid' attribute is used to specify native vlans if other than 1 +# 'bridge-access' attribute is used to declare access port +# +# 'spine-bond host-bond-0[1-2]' are clag bonds and will be considered by +# clagd for dual connection. clag-id has to be a non-zero and has to match +# across the peer switches for the bonds to become dual connected. + +# spine bond +# +auto spine-bond +iface spine-bond + bond-slaves glob swp19-22 + bond-mode 802.3ad + bond-miimon 100 + bond-use-carrier 1 + bond-lacp-rate 1 + bond-min-links 1 + bond-xmit-hash-policy layer3+4 + clag-id 100 + +# mlag bond and peer interface +# +auto peer-bond +iface peer-bond + bond-slaves glob swp23-24 + bond-mode 802.3ad + bond-miimon 100 + bond-use-carrier 1 + bond-lacp-rate 1 + bond-min-links 1 + bond-xmit-hash-policy layer3+4 + +# sub-interface for clagd communication +# +auto peer-bond.4094 +iface peer-bond.4094 + address 169.254.0.1/30 + clagd-peer-ip 169.254.0.2 + clagd-sys-mac 44:38:39:ff:00:01 + #clagd-priority 4096 + # Please see man clagd for more options + # clagd-args --peerTimeout 30 + +# host ports +# +auto host-bond-01 +iface host-bond-01 + bond-slaves swp1 + bond-mode 802.3ad + bond-miimon 100 + bond-use-carrier 1 + bond-lacp-rate 1 + bond-min-links 1 + bond-xmit-hash-policy layer3+4 + clag-id 1 + +auto host-bond-02 +iface host-bond-02 + bond-slaves swp2 + bond-mode 802.3ad + bond-miimon 100 + bond-use-carrier 1 + bond-lacp-rate 1 + bond-min-links 1 + bond-xmit-hash-policy layer3+4 + clag-id 2 + +# the bridge +auto bridge +iface bridge + bridge-vlan-aware yes + bridge-ports peer-bond spine-bond glob host-bond-0[1-2] + bridge-stp on + bridge-vids 10 20-23 diff --git a/ifupdown2/ifupdown/iface.py b/ifupdown2/ifupdown/iface.py index 93d5411..79840f9 100644 --- a/ifupdown2/ifupdown/iface.py +++ b/ifupdown2/ifupdown/iface.py @@ -17,6 +17,22 @@ from collections import OrderedDict import logging import json +class ifaceType(): + UNKNOWN = 0x0 + IFACE = 0x1 + BRIDGE_VLAN = 0x2 + +class ifaceLinkKind(): + UNKNOWN = 0x0 + BRIDGE = 0x1 + BOND = 0x2 + +class ifaceLinkType(): + LINK_UNKNOWN = 0x0 + LINK_SLAVE = 0x1 + LINK_MASTER = 0x2 + LINK_NA = 0x3 + class ifaceStatus(): """Enumerates iface status """ @@ -172,8 +188,10 @@ class iface(): """ # flag to indicate that the object was created from pickled state - _PICKLED = 0x1 - HAS_SIBLINGS = 0x2 + _PICKLED = 0x00000001 + HAS_SIBLINGS = 0x00000010 + IFACERANGE_ENTRY = 0x00000100 + IFACERANGE_START = 0x00001000 version = '0.1' @@ -185,13 +203,15 @@ class iface(): """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 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 + 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 """ @@ -203,6 +223,12 @@ class iface(): """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.realname = None + self.link_type = ifaceLinkType.LINK_UNKNOWN + self.link_kind = ifaceLinkKind.UNKNOWN def _set_attrs_from_dict(self, attrdict): self.auto = attrdict.get('auto', False) @@ -266,6 +292,17 @@ class iface(): 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) @@ -300,6 +337,17 @@ class iface(): """ 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) @@ -310,14 +358,23 @@ class iface(): 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: + 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. @@ -330,6 +387,7 @@ class iface(): 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 @@ -340,6 +398,7 @@ class iface(): if v != dstiface.config.get(k)): return False return True + def __getstate__(self): odict = self.__dict__.copy() del odict['state'] @@ -353,6 +412,8 @@ class iface(): del odict['raw_config'] del odict['linkstate'] del odict['env'] + del odict['link_type'] + del odict['link_kind'] return odict def __setstate__(self, dict): @@ -369,6 +430,8 @@ class iface(): self.priv_flags = 0 self.raw_config = [] self.flags |= self._PICKLED + self.link_type = ifaceLinkType.LINK_NA + self.link_kind = ifaceLinkKind.UNKNOWN def dump_raw(self, logger): indent = ' ' @@ -402,43 +465,60 @@ class iface(): logger.info('}') def dump_pretty(self, with_status=False, - successstr='success', errorstr='error'): + 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' %self.name - outbuf += 'iface %s' %self.name + 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: - outbuf += ' %s' %self.addr_family + ifaceline += ' %s' %self.addr_family if self.addr_method: - outbuf += ' %s' %self.addr_method + ifaceline += ' %s' %self.addr_method if with_status: - if (self.status == ifaceStatus.NOTFOUND or - self.status == ifaceStatus.ERROR): - outbuf += ' (%s)' %errorstr + 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: - outbuf += ' (%s)' %successstr + 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: - if with_status: - outbuf = (outbuf.encode('utf8') - if isinstance(outbuf, unicode) else outbuf) + outbuf = (outbuf.encode('utf8') + if isinstance(outbuf, unicode) else outbuf) print outbuf + '\n' return - outbuf += '\n' + else: + outbuf += ifaceline + '\n' config = self.config if config: for cname, cvaluelist in config.items(): idx = 0 for cv in cvaluelist: - if not cv: continue if with_status: s = self.get_config_attr_status(cname, idx) - if s: - outbuf += (indent + '%s %s (%s)\n' - %(cname, cv, errorstr)) + if s == -1: + status_str = unknownstr + elif s == 1: + status_str = errorstr elif s == 0: - outbuf += (indent + '%s %s (%s)\n' - %(cname, cv, successstr)) + 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 diff --git a/ifupdown2/ifupdown/iff.py b/ifupdown2/ifupdown/iff.py new file mode 100644 index 0000000..f191883 --- /dev/null +++ b/ifupdown2/ifupdown/iff.py @@ -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 diff --git a/ifupdown2/ifupdown/ifupdownbase.py b/ifupdown2/ifupdown/ifupdownbase.py index efccfeb..2a6b480 100644 --- a/ifupdown2/ifupdown/ifupdownbase.py +++ b/ifupdown2/ifupdown/ifupdownbase.py @@ -12,6 +12,7 @@ import subprocess import re import os from iface import * +import rtnetlink_api as rtnetlink_api class ifupdownBase(object): @@ -65,7 +66,7 @@ class ifupdownBase(object): return os.path.exists('/sys/class/net/%s' %ifacename) def link_up(self, ifacename): - self.exec_command('ifconfig %s up' %ifacename) + rtnetlink_api.rtnl_api.link_set(ifacename, "up") def link_down(self, ifacename): - self.exec_command('ifconfig %s down' %ifacename) + rtnetlink_api.rtnl_api.link_set(ifacename, "down") diff --git a/ifupdown2/ifupdown/ifupdownmain.py b/ifupdown2/ifupdown/ifupdownmain.py index 2c60bd1..691e5f4 100644 --- a/ifupdown2/ifupdown/ifupdownmain.py +++ b/ifupdown2/ifupdown/ifupdownmain.py @@ -34,8 +34,18 @@ from sets import Set _tickmark = u'\u2713' _crossmark = u'\u2717' -_success_sym = _tickmark -_error_sym = _crossmark +_success_sym = '(%s)' %_tickmark +_error_sym = '(%s)' %_crossmark + +class ifupdownFlags(): + FORCE = False + DRYRUN = False + NOWAIT = False + PERFMODE = False + CACHE = False + + # Flags + CACHE_FLAGS = 0x0 class ifupdownMain(ifupdownBase): """ ifupdown2 main class """ @@ -50,8 +60,8 @@ class ifupdownMain(ifupdownBase): ADDONS_ENABLE = False # priv flags to mark iface objects - BUILTIN = 0x1 - NOCONFIG = 0x2 + BUILTIN = 0x0001 + NOCONFIG = 0x0010 scripts_dir='/etc/network' addon_modules_dir='/usr/share/ifupdownaddons' @@ -98,14 +108,46 @@ class ifupdownMain(ifupdownBase): # Handlers for ops that ifupdown2 owns def run_up(self, ifaceobj): - ifacename = ifaceobj.name - if self.link_exists(ifacename): - self.link_up(ifacename) + # Skip link sets on ifaceobjs of type 'vlan' (used for l2 attrs). + # there is no real interface behind it + if ifaceobj.type == ifaceType.BRIDGE_VLAN: + return + if (ifaceobj.addr_method and + ifaceobj.addr_method == 'manual'): + return + if self._delay_admin_state: + self._delay_admin_state_iface_queue.append(ifaceobj.name) + return + # If this object is a link slave, ie its link is controlled + # by its link master interface, then dont set the link state. + # But do allow user to change state of the link if the interface + # is already with its link master (hence the master check). + if ifaceobj.link_type == ifaceLinkType.LINK_SLAVE: + return + if not self.link_exists(ifaceobj.name): + return + self.link_up(ifaceobj.name) def run_down(self, ifaceobj): - ifacename = ifaceobj.name - if self.link_exists(ifacename): - self.link_down(ifacename) + # Skip link sets on ifaceobjs of type 'vlan' (used for l2 attrs) + # there is no real interface behind it + if ifaceobj.type == ifaceType.BRIDGE_VLAN: + return + if (ifaceobj.addr_method and + ifaceobj.addr_method == 'manual'): + return + if self._delay_admin_state: + self._delay_admin_state_iface_queue.append(ifaceobj.name) + return + # If this object is a link slave, ie its link is controlled + # by its link master interface, then dont set the link state. + # But do allow user to change state of the link if the interface + # is already with its link master (hence the master check). + if ifaceobj.link_type == ifaceLinkType.LINK_SLAVE: + return + if not self.link_exists(ifaceobj.name): + return + self.link_down(ifaceobj.name) # ifupdown object interface operation handlers ops_handlers = OrderedDict([('up', run_up), @@ -155,11 +197,21 @@ class ifupdownMain(ifupdownBase): self.config = config self.logger.debug(self.config) + self.type = ifaceType.UNKNOWN + # Can be used to provide hints for caching self.CACHE_FLAGS = 0x0 self._DELETE_DEPENDENT_IFACES_WITH_NOCONFIG = False self.ADDONS_ENABLE = addons_enable + # Copy flags into ifupdownFlags + # XXX: before we transition fully to ifupdownFlags + ifupdownFlags.FORCE = force + ifupdownFlags.DRYRUN = dryrun + ifupdownFlags.NOWAIT = nowait + ifupdownFlags.PERFMODE = perfmode + ifupdownFlags.CACHE = cache + self.ifaces = OrderedDict() self.njobs = njobs self.pp = pprint.PrettyPrinter(indent=4) @@ -171,6 +223,8 @@ class ifupdownMain(ifupdownBase): self.load_scripts(self.scripts_dir) self.dependency_graph = OrderedDict({}) + self._cache_no_repeats = {} + if self.STATEMANAGER_ENABLE: try: self.statemanager = stateManager() @@ -181,10 +235,45 @@ class ifupdownMain(ifupdownBase): raise else: self.STATEMANAGER_UPDATE = False + self._delay_admin_state = True if self.config.get( + 'delay_admin_state_change', '0') == '1' else False + self._delay_admin_state_iface_queue = [] + if self._delay_admin_state: + self.logger.info('\'delay_admin_state_change\' is set. admin ' + + 'state changes will be delayed till the end.') + + self._link_master_slave = True if self.config.get( + 'link_master_slave', '0') == '1' else False + if self._link_master_slave: + self.logger.info('\'link_master_slave\' is set. slave admin ' + + 'state changes will be delayed till the ' + + 'masters admin state change.') + + def link_master_slave_ignore_error(self, errorstr): + # If link master slave flag is set, + # there may be cases where the lowerdev may not be + # up resulting in 'Network is down' error + # This can happen if the lowerdev is a LINK_SLAVE + # of another interface which is not up yet + # example of such a case: + # bringing up a vlan on a bond interface and the bond + # is a LINK_SLAVE of a bridge (in other words the bond is + # part of a bridge) which is not up yet + if self._link_master_slave: + if 'Network is down': + return True + return False def get_ifaceobjs(self, ifacename): return self.ifaceobjdict.get(ifacename) + def get_ifaceobjs_saved(self, ifacename): + """ Return ifaceobjects from statemanager """ + if self.STATEMANAGER_ENABLE: + return self.statemanager.get_ifaceobjs(ifacename) + else: + None + def get_ifaceobj_first(self, ifacename): ifaceobjs = self.get_ifaceobjs(ifacename) if ifaceobjs: @@ -197,6 +286,20 @@ class ifupdownMain(ifupdownBase): def get_iface_obj_last(self, ifacename): return self.ifaceobjdict.get(ifacename)[-1] + + def must_follow_upperifaces(self, ifacename): + # + # XXX: This bleeds the knowledge of iface + # types in the infrastructure module. + # Cant think of a better fix at the moment. + # In future maybe the module can set a flag + # to indicate if we should follow upperifaces + # + ifaceobj = self.get_ifaceobj_first(ifacename) + if ifaceobj.type == ifaceType.BRIDGE_VLAN: + return False + return True + def create_n_save_ifaceobj(self, ifacename, priv_flags=None, increfcnt=False): """ creates a iface object and adds it to the iface dictionary """ @@ -204,6 +307,8 @@ class ifupdownMain(ifupdownBase): ifaceobj.name = ifacename ifaceobj.priv_flags = priv_flags ifaceobj.auto = True + if not self._link_master_slave: + ifaceobj.link_type = ifaceLinkType.LINK_NA if increfcnt: ifaceobj.inc_refcnt() self.ifaceobjdict[ifacename] = [ifaceobj] @@ -215,6 +320,7 @@ class ifupdownMain(ifupdownBase): """ ifaceobjcurr = iface() ifaceobjcurr.name = ifaceobj.name + ifaceobjcurr.type = ifaceobj.type ifaceobjcurr.lowerifaces = ifaceobj.lowerifaces ifaceobjcurr.priv_flags = ifaceobj.priv_flags ifaceobjcurr.auto = ifaceobj.auto @@ -278,7 +384,7 @@ class ifupdownMain(ifupdownBase): if not ifaceobj: return True return self.is_ifaceobj_noconfig(ifaceobj) - def preprocess_dependency_list(self, upperifacename, dlist, ops): + def preprocess_dependency_list(self, upperifaceobj, dlist, ops): """ We go through the dependency list and delete or add interfaces from the interfaces dict by applying the following rules: @@ -298,29 +404,34 @@ class ifupdownMain(ifupdownBase): for d in dlist: dilist = self.get_ifaceobjs(d) if not dilist: + ni = None if self.is_iface_builtin_byname(d): - self.create_n_save_ifaceobj(d, self.BUILTIN | self.NOCONFIG, - True).add_to_upperifaces(upperifacename) + ni = self.create_n_save_ifaceobj(d, + self.BUILTIN | self.NOCONFIG, True) elif not self._DELETE_DEPENDENT_IFACES_WITH_NOCONFIG: - self.create_n_save_ifaceobj(d, self.NOCONFIG, - True).add_to_upperifaces(upperifacename) + ni = self.create_n_save_ifaceobj(d, self.NOCONFIG, + True) else: del_list.append(d) + if ni: + ni.add_to_upperifaces(upperifaceobj.name) + if upperifaceobj.link_type == ifaceLinkType.LINK_MASTER: + ni.link_type = ifaceLinkType.LINK_SLAVE else: for di in dilist: di.inc_refcnt() - di.add_to_upperifaces(upperifacename) - + di.add_to_upperifaces(upperifaceobj.name) + if upperifaceobj.link_type == ifaceLinkType.LINK_MASTER: + di.link_type = ifaceLinkType.LINK_SLAVE for d in del_list: dlist.remove(d) - def query_dependents(self, ifaceobj, ops): + def query_dependents(self, ifaceobj, ops, ifacenames, type=None): """ Gets iface dependents by calling into respective modules """ - dlist = None + ret_dlist = [] # Get dependents for interface by querying respective modules - for mname, module in self.modules.items(): - module = self.modules.get(mname) + for module in self.modules.values(): try: if ops[0] == 'query-running': if (not hasattr(module, @@ -331,15 +442,14 @@ class ifupdownMain(ifupdownBase): if (not hasattr(module, 'get_dependent_ifacenames')): continue dlist = module.get_dependent_ifacenames(ifaceobj, - self.ifaceobjdict.keys()) + ifacenames) except Exception, e: self.logger.warn('%s: error getting dependent interfaces (%s)' %(ifaceobj.name, str(e))) dlist = None pass - if dlist: - break - return dlist + if dlist: ret_dlist.extend(dlist) + return list(set(ret_dlist)) def populate_dependency_info(self, ops, ifacenames=None): """ recursive function to generate iface dependency info """ @@ -357,18 +467,39 @@ class ifupdownMain(ifupdownBase): continue dlist = ifaceobj.lowerifaces if not dlist: - dlist = self.query_dependents(ifaceobj, ops) + dlist = self.query_dependents(ifaceobj, ops, ifacenames) else: continue if dlist: - self.preprocess_dependency_list(ifaceobj.name, + self.preprocess_dependency_list(ifaceobj, dlist, ops) ifaceobj.lowerifaces = dlist [iqueue.append(d) for d in dlist] if not self.dependency_graph.get(i): self.dependency_graph[i] = dlist + def _check_config_no_repeats(self, ifaceobj): + """ check if object has an attribute that is + restricted to a single object in the system. + if yes, warn and return """ + for k,v in self._cache_no_repeats.items(): + iv = ifaceobj.config.get(k) + if iv and iv[0] == v: + self.logger.error('ignoring interface %s. ' %ifaceobj.name + + 'Only one object with attribute ' + + '\'%s %s\' allowed.' %(k, v)) + return True + for k, v in self.config.get('no_repeats', {}).items(): + iv = ifaceobj.config.get(k) + if iv and iv[0] == v: + self._cache_no_repeats[k] = v + return False + def _save_iface(self, ifaceobj): + if self._check_config_no_repeats(ifaceobj): + return + if not self._link_master_slave: + ifaceobj.link_type = ifaceLinkType.LINK_NA currentifaceobjlist = self.ifaceobjdict.get(ifaceobj.name) if not currentifaceobjlist: self.ifaceobjdict[ifaceobj.name]= [ifaceobj] @@ -376,8 +507,9 @@ class ifupdownMain(ifupdownBase): if ifaceobj.compare(currentifaceobjlist[0]): self.logger.warn('duplicate interface %s found' %ifaceobj.name) return - currentifaceobjlist[0].flags |= iface.HAS_SIBLINGS - ifaceobj.flags |= iface.HAS_SIBLINGS + if currentifaceobjlist[0].type == ifaceobj.type: + currentifaceobjlist[0].flags |= iface.HAS_SIBLINGS + ifaceobj.flags |= iface.HAS_SIBLINGS self.ifaceobjdict[ifaceobj.name].append(ifaceobj) def _iface_configattr_syntax_checker(self, attrname, attrval): @@ -394,7 +526,7 @@ class ifupdownMain(ifupdownBase): def _ifaceobj_syntax_checker(self, ifaceobj): err = False - for attrname in ifaceobj.config: + for attrname, attrvalue in ifaceobj.config.items(): found = False for k, v in self.module_attrs.items(): if v and v.get('attrs', {}).get(attrname): @@ -430,10 +562,16 @@ class ifupdownMain(ifupdownBase): with open(self.addon_modules_configfile, 'r') as f: lines = f.readlines() for l in lines: - litems = l.rstrip(' \n\t\r').split(',') - operation = litems[0] - mname = litems[1] - self.module_ops[operation].append(mname) + try: + litems = l.strip(' \n\t\r').split(',') + if not litems or len(litems) < 2: + continue + operation = litems[0] + mname = litems[1] + self.module_ops[operation].append(mname) + except Exception, e: + self.logger.warn('error reading line \'%s\'' %(l, str(e))) + continue def load_addon_modules(self, modules_dir): """ load python modules from modules_dir @@ -547,10 +685,9 @@ class ifupdownMain(ifupdownBase): # continue reading pass - def _sched_ifaces(self, ifacenames, ops): + def _sched_ifaces(self, ifacenames, ops, skipupperifaces=False): self.logger.debug('scheduling \'%s\' for %s' %(str(ops), str(ifacenames))) - self._pretty_print_ordered_dict('dependency graph', self.dependency_graph) return ifaceScheduler.sched_ifaces(self, ifacenames, ops, @@ -558,22 +695,47 @@ class ifupdownMain(ifupdownBase): order=ifaceSchedulerFlags.INORDER if 'down' in ops[0] else ifaceSchedulerFlags.POSTORDER, - followdependents=True if self.WITH_DEPENDS else False) + followdependents=True if self.WITH_DEPENDS else False, + skipupperifaces=skipupperifaces) - def _validate_ifaces(self, ifacenames): + def _render_ifacename(self, ifacename): + new_ifacenames = [] + vlan_match = re.match("^([\d]+)-([\d]+)", ifacename) + if vlan_match: + vlan_groups = vlan_match.groups() + if vlan_groups[0] and vlan_groups[1]: + [new_ifacenames.append('%d' %v) + for v in range(int(vlan_groups[0]), + int(vlan_groups[1])+1)] + return new_ifacenames + + def _preprocess_ifacenames(self, ifacenames): """ validates interface list for config existance. returns -1 if one or more interface not found. else, returns 0 """ + new_ifacenames = [] err_iface = '' for i in ifacenames: ifaceobjs = self.get_ifaceobjs(i) if not ifaceobjs: - err_iface += ' ' + i + # if name not available, render interface name and check again + rendered_ifacenames = utils.expand_iface_range(i) + if rendered_ifacenames: + for ri in rendered_ifacenames: + ifaceobjs = self.get_ifaceobjs(ri) + if not ifaceobjs: + err_iface += ' ' + ri + else: + new_ifacenames.append(ri) + else: + err_iface += ' ' + i + else: + new_ifacenames.append(i) if err_iface: raise Exception('cannot find interfaces:%s' %err_iface) - return True + return new_ifacenames def _iface_whitelisted(self, auto, allow_classes, excludepats, ifacename): """ Checks if interface is whitelisted depending on set of parameters. @@ -581,7 +743,6 @@ class ifupdownMain(ifupdownBase): interfaces are checked against the allow_classes and auto lists. """ - if excludepats: for e in excludepats: if re.search(e, ifacename): @@ -643,12 +804,39 @@ class ifupdownMain(ifupdownBase): traceback.print_tb(t) self.logger.warning('error saving state (%s)' %str(e)) + def set_type(self, type): + if type == 'iface': + self.type = ifaceType.IFACE + elif type == 'vlan': + self.type = ifaceType.BRIDGE_VLAN + else: + self.type = ifaceType.UNKNOWN + + def _process_delay_admin_state_queue(self, op): + if not self._delay_admin_state_iface_queue: + return + if op == 'up': + func = self.link_up + elif op == 'down': + func = self.link_down + else: + return + for i in self._delay_admin_state_iface_queue: + try: + if self.link_exists(i): + func(i) + except Exception, e: + self.logger.warn(str(e)) + pass + def up(self, ops, auto=False, allow_classes=None, ifacenames=None, - excludepats=None, printdependency=None, syntaxcheck=False): + excludepats=None, printdependency=None, syntaxcheck=False, + type=None, skipupperifaces=False): """This brings the interface(s) up Args: - ops (list): list of ops to perform on the interface(s). Eg: ['pre-up', 'up', 'post-up' + ops (list): list of ops to perform on the interface(s). + Eg: ['pre-up', 'up', 'post-up' Kwargs: auto (bool): act on interfaces marked auto @@ -658,6 +846,8 @@ class ifupdownMain(ifupdownBase): syntaxcheck (bool): only perform syntax check """ + self.set_type(type) + if allow_classes: self.IFACE_CLASS = True if not self.ADDONS_ENABLE: self.STATEMANAGER_UPDATE = False @@ -674,9 +864,7 @@ class ifupdownMain(ifupdownBase): return if ifacenames: - # If iface list is given by the caller, always check if iface - # is present - self._validate_ifaces(ifacenames) + ifacenames = self._preprocess_ifacenames(ifacenames) # if iface list not given by user, assume all from config file if not ifacenames: ifacenames = self.ifaceobjdict.keys() @@ -696,15 +884,20 @@ class ifupdownMain(ifupdownBase): self.populate_dependency_info(ops) try: - self._sched_ifaces(filtered_ifacenames, ops) + self._sched_ifaces(filtered_ifacenames, ops, + skipupperifaces=skipupperifaces) finally: + self._process_delay_admin_state_queue('up') if not self.DRYRUN and self.ADDONS_ENABLE: self._save_state() def down(self, ops, auto=False, allow_classes=None, ifacenames=None, - excludepats=None, printdependency=None, usecurrentconfig=False): + excludepats=None, printdependency=None, usecurrentconfig=False, + type=None): """ down an interface """ + self.set_type(type) + if allow_classes: self.IFACE_CLASS = True if not self.ADDONS_ENABLE: self.STATEMANAGER_UPDATE = False @@ -729,7 +922,7 @@ class ifupdownMain(ifupdownBase): # If iface list is given by the caller, always check if iface # is present try: - self._validate_ifaces(ifacenames) + ifacenames = self._preprocess_ifacenames(ifacenames) except Exception, e: raise Exception('%s' %str(e) + ' (interface was probably never up ?)') @@ -743,7 +936,7 @@ class ifupdownMain(ifupdownBase): excludepats, i)] if not filtered_ifacenames: raise Exception('no ifaces found matching given allow lists ' + - '(interfaces were probably never up)') + '(or interfaces were probably never up ?)') if printdependency: self.populate_dependency_info(ops, filtered_ifacenames) @@ -755,14 +948,17 @@ class ifupdownMain(ifupdownBase): try: self._sched_ifaces(filtered_ifacenames, ops) finally: + self._process_delay_admin_state_queue('down') if not self.DRYRUN and self.ADDONS_ENABLE: self._save_state() def query(self, ops, auto=False, allow_classes=None, ifacenames=None, excludepats=None, printdependency=None, - format='native'): + format='native', type=None): """ query an interface """ + self.set_type(type) + if allow_classes: self.IFACE_CLASS = True if self.STATEMANAGER_ENABLE and ops[0] == 'query-savedstate': @@ -787,8 +983,8 @@ class ifupdownMain(ifupdownBase): raise if ifacenames and ops[0] != 'query-running': - # If iface list is given, always check if iface is present - self._validate_ifaces(ifacenames) + # If iface list is given, always check if iface is present + ifacenames = self._preprocess_ifacenames(ifacenames) # if iface list not given by user, assume all from config file if not ifacenames: ifacenames = self.ifaceobjdict.keys() @@ -804,7 +1000,7 @@ class ifupdownMain(ifupdownBase): raise Exception('no ifaces found matching ' + 'given allow lists') - self.populate_dependency_info(ops, filtered_ifacenames) + self.populate_dependency_info(ops) if ops[0] == 'query-dependency' and printdependency: self.print_dependency(filtered_ifacenames, printdependency) return @@ -825,17 +1021,91 @@ class ifupdownMain(ifupdownBase): self.print_ifaceobjsrunning_pretty(filtered_ifacenames, format) return - def reload(self, upops, downops, auto=False, allow=None, - ifacenames=None, excludepats=None, usecurrentconfig=False): + def _reload_currentlyup(self, upops, downops, auto=True, allow=None, + ifacenames=None, excludepats=None, usecurrentconfig=False, + **extra_args): + """ reload currently up interfaces """ + allow_classes = [] + new_ifaceobjdict = {} + + # Override auto to true + auto = True + if auto: + self.ALL = True + self.WITH_DEPENDS = True + try: + self.read_iface_config() + except: + raise + if not self.ifaceobjdict: + self.logger.warn("nothing to reload ..exiting.") + return + already_up_ifacenames = [] + # generate dependency graph of interfaces + self.populate_dependency_info(upops) + if (not usecurrentconfig and self.STATEMANAGER_ENABLE + and self.statemanager.ifaceobjdict): + already_up_ifacenames = self.statemanager.ifaceobjdict.keys() + + if not ifacenames: ifacenames = self.ifaceobjdict.keys() + filtered_ifacenames = [i for i in ifacenames + if self._iface_whitelisted(auto, allow_classes, + excludepats, i)] + + # Get already up interfaces that still exist in the interfaces file + already_up_ifacenames_not_present = Set( + already_up_ifacenames).difference(ifacenames) + already_up_ifacenames_still_present = Set( + already_up_ifacenames).difference( + already_up_ifacenames_not_present) + interfaces_to_up = Set(already_up_ifacenames_still_present).union( + filtered_ifacenames) + + if (already_up_ifacenames_not_present and + self.config.get('ifreload_currentlyup_down_notpresent') == '1'): + self.logger.info('reload: schedule down on interfaces: %s' + %str(already_up_ifacenames_not_present)) + + # Save a copy of new iface objects and dependency_graph + new_ifaceobjdict = dict(self.ifaceobjdict) + new_dependency_graph = dict(self.dependency_graph) + + # old interface config is read into self.ifaceobjdict + self.read_old_iface_config() + + # reinitialize dependency graph + self.dependency_graph = OrderedDict({}) + self.populate_dependency_info(downops, + already_up_ifacenames_not_present) + self._sched_ifaces(already_up_ifacenames_not_present, downops) + else: + self.logger.debug('no interfaces to down ..') + + # Now, run 'up' with new config dict + # reset statemanager update flag to default + if new_ifaceobjdict: + self.ifaceobjdict = new_ifaceobjdict + self.dependency_graph = new_dependency_graph + + if not self.ifaceobjdict: + return + self.logger.info('reload: scheduling up on interfaces: %s' + %str(interfaces_to_up)) + self._sched_ifaces(interfaces_to_up, upops) + if self.DRYRUN: + return + self._save_state() + + def _reload_default(self, upops, downops, auto=False, allow=None, + ifacenames=None, excludepats=None, usecurrentconfig=False, + **extra_args): """ reload interface config """ allow_classes = [] new_ifaceobjdict = {} - self.logger.debug('reloading interface config ..') if auto: self.ALL = True self.WITH_DEPENDS = True - try: self.read_iface_config() except: @@ -844,7 +1114,6 @@ class ifupdownMain(ifupdownBase): if not self.ifaceobjdict: self.logger.warn("nothing to reload ..exiting.") return - # generate dependency graph of interfaces self.populate_dependency_info(upops) if (not usecurrentconfig and self.STATEMANAGER_ENABLE @@ -875,7 +1144,8 @@ class ifupdownMain(ifupdownBase): # config # ifacedownlist = [] - for ifname, lastifaceobjlist in self.ifaceobjdict.items(): + for ifname in filtered_ifacenames: + lastifaceobjlist = self.ifaceobjdict.get(ifname) objidx = 0 # If interface is not present in the new file # append it to the down list @@ -897,13 +1167,19 @@ class ifupdownMain(ifupdownBase): continue if ifacedownlist: - self.logger.info('Executing down on interfaces: %s' + self.logger.info('reload: scheduling down on interfaces: %s' %str(ifacedownlist)) # reinitialize dependency graph self.dependency_graph = OrderedDict({}) # Generate dependency info for old config self.populate_dependency_info(downops, ifacedownlist) - self._sched_ifaces(ifacedownlist, downops) + try: + self._sched_ifaces(ifacedownlist, downops) + except Exception, e: + self.logger.error(str(e)) + pass + finally: + self._process_delay_admin_state_queue('down') else: self.logger.debug('no interfaces to down ..') @@ -918,13 +1194,27 @@ class ifupdownMain(ifupdownBase): if self._iface_whitelisted(auto, allow_classes, excludepats, i)] - self.logger.info('Scheduling up on interfaces: %s' - %str(filtered_ifacenames)) - self._sched_ifaces(filtered_ifacenames, upops) + self.logger.info('reload: scheduling up on interfaces: %s' + %str(filtered_ifacenames)) + try: + self._sched_ifaces(filtered_ifacenames, upops) + except Exception, e: + self.logger.error(str(e)) + pass + finally: + self._process_delay_admin_state_queue('up') if self.DRYRUN: return self._save_state() + def reload(self, *args, **kargs): + """ reload interface config """ + self.logger.debug('reloading interface config ..') + if kargs.get('currentlyup', False): + self._reload_currentlyup(*args, **kargs) + else: + self._reload_default(*args, **kargs) + def _pretty_print_ordered_dict(self, prefix, argdict): outbuf = prefix + ' {\n' for k, vlist in argdict.items(): @@ -985,7 +1275,14 @@ class ifupdownMain(ifupdownBase): print json.dumps(ifaceobjs, cls=ifaceJsonEncoder, indent=4, separators=(',', ': ')) else: - map(lambda i: i.dump_pretty(), ifaceobjs) + expand = int(self.config.get('ifquery_ifacename_expand_range', '0')) + for i in ifaceobjs: + if not expand and (i.flags & iface.IFACERANGE_ENTRY): + # print only the first one + if i.flags & iface.IFACERANGE_START: + i.dump_pretty(use_realname=True) + else: + i.dump_pretty() def _get_ifaceobjscurr_pretty(self, ifacenames, ifaceobjs): ret = 0 @@ -1016,16 +1313,16 @@ class ifupdownMain(ifupdownBase): ifaceobjs = [] ret = self._get_ifaceobjscurr_pretty(ifacenames, ifaceobjs) if not ifaceobjs: return - self.logger.debug(ifaceobjs) if format == 'json': print json.dumps(ifaceobjs, cls=ifaceJsonEncoder, indent=2, separators=(',', ': ')) else: map(lambda i: i.dump_pretty(with_status=True, - successstr=self.config.get('check_success_str', - _success_sym), - errorstr=self.config.get('check_error_str', _error_sym)), - ifaceobjs) + successstr=self.config.get('ifquery_check_success_str', + _success_sym), + errorstr=self.config.get('ifquery_check_error_str', _error_sym), + unknownstr=self.config.get('ifquery_check_unknown_str', '')), + ifaceobjs) return ret def print_ifaceobjsrunning_pretty(self, ifacenames, format='native'): diff --git a/ifupdown2/ifupdown/netlink.py b/ifupdown2/ifupdown/netlink.py new file mode 100644 index 0000000..5a01043 --- /dev/null +++ b/ifupdown2/ifupdown/netlink.py @@ -0,0 +1,241 @@ +#!/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 socket.error as (errno, string): + raise NetlinkError("open: socket err[%d]: %s" % \ + (errno, string)) + + def bind(self, groups, cb): + + self._nl_cb = cb + + try: + socket.socket.bind(self, (self.pid, groups)) + + except socket.error as (errno, string): + raise NetlinkError("bind: socket err[%d]: %s" % \ + (errno, string)) + + def sendall(self, string): + try: + socket.socket.sendall(self, string) + except socket.error as (errno, string): + raise NetlinkError("send: socket err[%d]: %s" % \ + (errno, string)) + + 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 socket.error as (errno, string): + if errno in [EINTR, EAGAIN]: + return False + raise NetlinkError("netlink: socket err[%d]: %s" % \ + (errno, string)) + + 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() diff --git a/ifupdown2/ifupdown/networkinterfaces.py b/ifupdown2/ifupdown/networkinterfaces.py index 46f9053..f241e45 100644 --- a/ifupdown2/ifupdown/networkinterfaces.py +++ b/ifupdown2/ifupdown/networkinterfaces.py @@ -12,6 +12,8 @@ import logging import glob import re import os +import copy +from utils import utils from iface import * from template import templateEngine @@ -23,6 +25,7 @@ class networkInterfaces(): hotplugs = {} auto_ifaces = [] callbacks = {} + auto_all = False _addrfams = {'inet' : ['static', 'manual', 'loopback', 'dhcp', 'dhcp6'], 'inet6' : ['static', 'manual', 'loopback', 'dhcp', 'dhcp6']} @@ -74,6 +77,12 @@ class networkInterfaces(): 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): @@ -138,7 +147,12 @@ class networkInterfaces(): 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: - for f in glob.glob(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, @@ -151,14 +165,23 @@ class networkInterfaces(): self._parse_error(self._currentfile, lineno, 'invalid auto line \'%s\''%lines[cur_idx]) return 0 - [self.auto_ifaces.append(a) for a in auto_ifaces] + 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): + if not self.callbacks.get('validateifaceattr')(newattrname, + attrval): self._parse_error(self._currentfile, lineno, 'iface %s: unsupported keyword (%s)' %(ifacename, attrname)) @@ -188,17 +211,26 @@ class networkInterfaces(): else: iface_config[newattrname].append(attrval) - def process_iface(self, lines, cur_idx, lineno): + def parse_iface(self, lines, cur_idx, lineno, ifaceobj): lines_consumed = 0 line_idx = cur_idx - ifaceobj = iface() 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) @@ -238,22 +270,60 @@ class networkInterfaces(): pass self._validate_addr_family(ifaceobj, lineno) - if ifaceobj.name in self.auto_ifaces: + 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] - - # Call iface found callback - self.callbacks.get('iface_found')(ifaceobj) + 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} + 'iface' : process_iface, + 'vlan' : process_vlan} def _is_keyword(self, str): # The additional split here is for allow- keyword @@ -274,6 +344,10 @@ class networkInterfaces(): 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') @@ -299,15 +373,13 @@ class networkInterfaces(): def read_filedata(self, filedata): self._currentfile_has_template = False - # process line continuations - filedata = ' '.join(d.strip() for d in filedata.split('\\')) # run through template engine try: rendered_filedata = self._template_engine.render(filedata) if rendered_filedata is filedata: - self._currentfile_has_template = True - else: self._currentfile_has_template = False + else: + self._currentfile_has_template = True except Exception, e: self._parse_error(self._currentfile, -1, 'failed to render template (%s). ' %str(e) + @@ -340,6 +412,11 @@ class networkInterfaces(): 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: diff --git a/ifupdown2/ifupdown/rtnetlink.py b/ifupdown2/ifupdown/rtnetlink.py new file mode 100644 index 0000000..9b13ad5 --- /dev/null +++ b/ifupdown2/ifupdown/rtnetlink.py @@ -0,0 +1,860 @@ +#!/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 diff --git a/ifupdown2/ifupdown/rtnetlink_api.py b/ifupdown2/ifupdown/rtnetlink_api.py new file mode 100644 index 0000000..f9bba10 --- /dev/null +++ b/ifupdown2/ifupdown/rtnetlink_api.py @@ -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, 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, 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, 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, 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()) diff --git a/ifupdown2/ifupdown/scheduler.py b/ifupdown2/ifupdown/scheduler.py index 7782f12..4b4bb57 100644 --- a/ifupdown2/ifupdown/scheduler.py +++ b/ifupdown2/ifupdown/scheduler.py @@ -19,6 +19,7 @@ from graph import * from collections import deque from threading import * from ifupdownbase import * +from sets import Set class ifaceSchedulerFlags(): """ Enumerates scheduler flags """ @@ -43,13 +44,19 @@ class ifaceScheduler(): """ Runs sub operation on an interface """ ifacename = ifaceobj.name - if (cls._STATE_CHECK and - (ifaceobj.state >= ifaceState.from_str(op))): - ifupdownobj.logger.debug('%s: already in state %s' %(ifacename, op)) + 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 @@ -62,15 +69,18 @@ class ifaceScheduler(): if (ifaceobj.priv_flags & ifupdownobj.NOCONFIG): continue ifupdownobj.logger.debug(msg) - m.run(ifaceobj, op, query_ifaceobj) + m.run(ifaceobj, op, query_ifaceobj, + ifaceobj_getfunc=ifupdownobj.get_ifaceobjs) else: ifupdownobj.logger.debug(msg) - m.run(ifaceobj, op) + m.run(ifaceobj, op, + ifaceobj_getfunc=ifupdownobj.get_ifaceobjs) except Exception, e: - err = 1 - ifupdownobj.log_error(str(e)) - err = 0 # error can be ignored by log_error, in which case - # reset err flag + 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), @@ -78,10 +88,15 @@ class ifaceScheduler(): 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), - ifaceStatus.SUCCESS) + status) - if ifupdownobj.COMPAT_EXEC_SCRIPTS: + 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' @@ -96,10 +111,13 @@ class ifaceScheduler(): """ 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 @@ -115,17 +133,26 @@ class ifaceScheduler(): # for the first object in the list handler = ifupdownobj.ops_handlers.get(op) if handler: - if (not ifaceobjs[0].addr_method or - (ifaceobjs[0].addr_method and - ifaceobjs[0].addr_method != 'manual')): + try: handler(ifupdownobj, ifaceobjs[0]) + except Exception, 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.COMPAT_EXEC_SCRIPTS else None) - posthookfunc = ifupdownobj.sched_hooks.get('posthook') - if posthookfunc: - posthookfunc(ifupdownobj, 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, e: + ifupdownobj.logger.warn('%s' %str(e)) + pass @classmethod def _check_upperifaces(cls, ifupdownobj, ifaceobj, ops, parent, @@ -182,10 +209,16 @@ class ifaceScheduler(): 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 + return # If inorder, run the iface first and then its dependents if order == ifaceSchedulerFlags.INORDER: @@ -258,6 +291,11 @@ class ifaceScheduler(): 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) @@ -294,6 +332,66 @@ class ifaceScheduler(): 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, e: + if continueonfailure: + self.logger.warn('%s' %str(e)) + @classmethod def get_sorted_iface_list(cls, ifupdownobj, ifacenames, ops, dependency_graph, indegrees=None): @@ -321,7 +419,7 @@ class ifaceScheduler(): def sched_ifaces(cls, ifupdownobj, ifacenames, ops, dependency_graph=None, indegrees=None, order=ifaceSchedulerFlags.POSTORDER, - followdependents=True): + followdependents=True, skipupperifaces=False): """ runs interface configuration modules on interfaces passed as argument. Runs topological sort on interface dependency graph. @@ -354,7 +452,7 @@ class ifaceScheduler(): # # Run any upperifaces if available # - followupperifaces = [] + followupperifaces = False run_queue = [] skip_ifacesort = int(ifupdownobj.config.get('skip_ifacesort', '0')) if not skip_ifacesort and not indegrees: @@ -366,7 +464,9 @@ class ifaceScheduler(): # If there is any interface that does exist, maybe it is a # logical interface and we have to followupperifaces when it # comes up, so get that list. - followupperifaces = (True if + 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) @@ -403,14 +503,14 @@ class ifaceScheduler(): run_queue.reverse() # run interface list - ifupdownobj.logger.info('running interfaces: %s' %str(run_queue)) cls.run_iface_list(ifupdownobj, run_queue, ops, parent=None, order=order, followdependents=followdependents) if not cls._SCHED_RETVAL: raise Exception() - if (ifupdownobj.config.get('skip_upperifaces', '0') == '0' and + if (not skipupperifaces and + ifupdownobj.config.get('skip_upperifaces', '0') == '0' and ((not ifupdownobj.ALL and followdependents) or followupperifaces) and 'up' in ops[0]): @@ -419,6 +519,5 @@ class ifaceScheduler(): ifupdownobj.logger.info('running upperifaces (parent interfaces) ' + 'if available ..') cls._STATE_CHECK = False - cls.run_iface_list_upper(ifupdownobj, ifacenames, ops, - skip_root=True) + cls.run_upperifaces(ifupdownobj, ifacenames, ops) cls._STATE_CHECK = True diff --git a/ifupdown2/ifupdown/statemanager.py b/ifupdown2/ifupdown/statemanager.py index 83d6e52..ef9ca7f 100644 --- a/ifupdown2/ifupdown/statemanager.py +++ b/ifupdown2/ifupdown/statemanager.py @@ -100,7 +100,8 @@ class stateManager(): op (str): ifupdown operation """ - self.logger.debug('%s: statemanager sync state' %ifaceobj.name) + 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: diff --git a/ifupdown2/ifupdown/utils.py b/ifupdown2/ifupdown/utils.py index 9dd6910..caf6583 100644 --- a/ifupdown2/ifupdown/utils.py +++ b/ifupdown2/ifupdown/utils.py @@ -8,6 +8,7 @@ # import os import fcntl +import re class utils(): @@ -29,4 +30,23 @@ class utils(): 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 + diff --git a/ifupdown2/ifupdownaddons/__init__.py b/ifupdown2/ifupdownaddons/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ifupdown2/ifupdownaddons/bridgeutils.py b/ifupdown2/ifupdownaddons/bridgeutils.py new file mode 100644 index 0000000..73f28b1 --- /dev/null +++ b/ifupdown2/ifupdownaddons/bridgeutils.py @@ -0,0 +1,501 @@ +#!/usr/bin/python +# +# Copyright 2014 Cumulus Networks, Inc. All rights reserved. +# Author: Roopa Prabhu, roopa@cumulusnetworks.com +# + +from ifupdown.iface import * +from utilsbase import * +import os +import re +import logging +from cache import * + +class brctl(utilsBase): + """ This class contains helper functions to interact with the bridgeutils + commands """ + + _cache_fill_done = False + + def __init__(self, *args, **kargs): + utilsBase.__init__(self, *args, **kargs) + if self.CACHE and not brctl._cache_fill_done: + self._bridge_fill() + brctl._cache_fill_done = True + + + def _bridge_get_mcattrs_from_sysfs(self, bridgename): + mcattrs = {} + mcattrmap = {'mclmc': 'multicast_last_member_count', + 'mcrouter': 'multicast_router', + 'mcsnoop' : 'multicast_snooping', + 'mcsqc' : 'multicast_startup_query_count', + 'mcqifaddr' : 'multicast_query_use_ifaddr', + 'mcquerier' : 'multicast_querier', + 'hashel' : 'hash_elasticity', + 'hashmax' : 'hash_max', + 'mclmi' : 'multicast_last_member_interval', + 'mcmi' : 'multicast_membership_interval', + 'mcqpi' : 'multicast_querier_interval', + 'mcqi' : 'multicast_query_interval', + 'mcqri' : 'multicast_query_response_interval', + 'mcsqi' : 'multicast_startup_query_interval'} + + mcattrsdivby100 = ['mclmi', 'mcmi', 'mcqpi', 'mcqi', 'mcqri', 'mcsqi'] + + for m, s in mcattrmap.items(): + n = self.read_file_oneline('/sys/class/net/%s/bridge/%s' + %(bridgename, s)) + if m in mcattrsdivby100: + try: + v = int(n) / 100 + mcattrs[m] = str(v) + except Exception, e: + self.logger.warn('error getting mc attr %s (%s)' + %(m, str(e))) + pass + else: + mcattrs[m] = n + return mcattrs + + def _bridge_attrs_fill(self, bridgename): + battrs = {} + bports = {} + + brout = self.exec_command('/sbin/brctl showstp %s' %bridgename) + chunks = re.split(r'\n\n', brout, maxsplit=0, flags=re.MULTILINE) + + try: + # Get all bridge attributes + broutlines = chunks[0].splitlines() + #battrs['pathcost'] = broutlines[3].split('path cost')[1].strip() + battrs['maxage'] = broutlines[4].split( + 'bridge max age')[1].strip().replace('.00', '') + battrs['hello'] = broutlines[5].split( + 'bridge hello time')[1].strip().replace('.00', + '') + battrs['fd'] = broutlines[6].split( + 'bridge forward delay')[1].strip( + ).replace('.00', '') + battrs.update(self._bridge_get_mcattrs_from_sysfs(bridgename)) + + # XXX: comment this out until mc attributes become available + # with brctl again + #battrs['hashel'] = broutlines[10].split('hash elasticity')[1].split()[0].strip() + #battrs['hashmax'] = broutlines[10].split('hash max')[1].strip() + #battrs['mclmc'] = broutlines[11].split('mc last member count')[1].split()[0].strip() + #battrs['mciqc'] = broutlines[11].split('mc init query count')[1].strip() + #battrs['mcrouter'] = broutlines[12].split('mc router')[1].split()[0].strip() + ##battrs['mcsnoop'] = broutlines[12].split('mc snooping')[1].strip() + #battrs['mclmt'] = broutlines[13].split('mc last member timer')[1].split()[0].strip() + except Exception, e: + self.logger.warn(str(e)) + pass + + linkCache.update_attrdict([bridgename, 'linkinfo'], battrs) + + for cidx in range(1, len(chunks)): + bpout = chunks[cidx].lstrip('\n') + if not bpout or bpout[0] == ' ': + continue + bplines = bpout.splitlines() + pname = bplines[0].split()[0] + bportattrs = {} + try: + bportattrs['pathcost'] = bplines[2].split( + 'path cost')[1].strip() + bportattrs['fdelay'] = bplines[4].split( + 'forward delay timer')[1].strip() + bportattrs['mcrouter'] = self.read_file_oneline( + '/sys/class/net/%s/brport/multicast_router' %pname) + bportattrs['mcfl'] = self.read_file_oneline( + '/sys/class/net/%s/brport/multicast_fast_leave' %pname) + + #bportattrs['mcrouters'] = bplines[6].split('mc router')[1].split()[0].strip() + #bportattrs['mc fast leave'] = bplines[6].split('mc fast leave')[1].strip() + except Exception, e: + self.logger.warn(str(e)) + pass + bports[pname] = bportattrs + linkCache.update_attrdict([bridgename, 'linkinfo', 'ports'], bports) + + def _bridge_fill(self, bridgename=None, refresh=False): + try: + # if cache is already filled, return + linkCache.get_attr([bridgename, 'linkinfo', 'fd']) + return + except: + pass + if not bridgename: + brctlout = self.exec_command('/sbin/brctl show') + else: + brctlout = self.exec_command('/sbin/brctl show ' + bridgename) + if not brctlout: + return + + for bline in brctlout.splitlines()[1:]: + bitems = bline.split() + if len(bitems) < 2: + continue + try: + linkCache.update_attrdict([bitems[0], 'linkinfo'], + {'stp' : bitems[2]}) + except KeyError: + linkCache.update_attrdict([bitems[0]], + {'linkinfo' : {'stp' : bitems[2]}}) + self._bridge_attrs_fill(bitems[0]) + + def _cache_get(self, attrlist, refresh=False): + try: + if self.DRYRUN: + return None + if self.CACHE: + if not self._cache_fill_done: + self._bridge_fill() + self._cache_fill_done = True + return linkCache.get_attr(attrlist) + if not refresh: + return linkCache.get_attr(attrlist) + self._bridge_fill(attrlist[0], refresh) + return linkCache.get_attr(attrlist) + except Exception, e: + self.logger.debug('_cache_get(%s) : [%s]' + %(str(attrlist), str(e))) + pass + return None + + def _cache_check(self, attrlist, value, refresh=False): + try: + attrvalue = self._cache_get(attrlist, refresh) + if attrvalue and attrvalue == value: + return True + except Exception, e: + self.logger.debug('_cache_check(%s) : [%s]' + %(str(attrlist), str(e))) + pass + return False + + def _cache_update(self, attrlist, value): + if self.DRYRUN: return + try: + linkCache.add_attr(attrlist, value) + except: + pass + + def _cache_delete(self, attrlist): + if self.DRYRUN: return + try: + linkCache.del_attr(attrlist) + except: + pass + + def _cache_invalidate(self): + if self.DRYRUN: return + linkCache.invalidate() + + def create_bridge(self, bridgename): + if self.bridge_exists(bridgename): + return + self.exec_command('/sbin/brctl addbr %s' %bridgename) + self._cache_update([bridgename], {}) + + def delete_bridge(self, bridgename): + if not self.bridge_exists(bridgename): + return + self.exec_command('/sbin/brctl delbr %s' %bridgename) + self._cache_invalidate() + + def add_bridge_port(self, bridgename, bridgeportname): + """ Add port to bridge """ + ports = self._cache_get([bridgename, 'linkinfo', 'ports']) + if ports and ports.get(bridgeportname): + return + self.exec_command('/sbin/brctl addif ' + bridgename + ' ' + + bridgeportname) + self._cache_update([bridgename, 'linkinfo', 'ports', + bridgeportname], {}) + + def delete_bridge_port(self, bridgename, bridgeportname): + """ Delete port from bridge """ + ports = self._cache_get([bridgename, 'linkinfo', 'ports']) + if not ports or not ports.get(bridgeportname): + return + self.exec_command('/sbin/brctl delif ' + bridgename + ' ' + + bridgeportname) + self._cache_delete([bridgename, 'linkinfo', 'ports', + 'bridgeportname']) + + def set_bridgeport_attrs(self, bridgename, bridgeportname, attrdict): + portattrs = self._cache_get([bridgename, 'linkinfo', + 'ports', bridgeportname]) + if portattrs == None: portattrs = {} + for k, v in attrdict.iteritems(): + if self.CACHE: + curval = portattrs.get(k) + if curval and curval == v: + continue + self.exec_command('/sbin/brctl set%s %s %s %s' + %(k, bridgename, bridgeportname, v)) + + def set_bridgeport_attr(self, bridgename, bridgeportname, + attrname, attrval): + if self._cache_check([bridgename, 'linkinfo', 'ports', + bridgeportname, attrname], attrval): + return + self.exec_command('/sbin/brctl set%s %s %s %s' %(attrname, bridgename, + bridgeportname, attrval)) + + def set_bridge_attrs(self, bridgename, attrdict): + for k, v in attrdict.iteritems(): + if not v: + continue + if self._cache_check([bridgename, 'linkinfo', k], v): + continue + try: + self.exec_command('/sbin/brctl set%s %s %s' + %(k, bridgename, v)) + except Exception, e: + self.logger.warn('%s: %s' %(bridgename, str(e))) + pass + + def set_bridge_attr(self, bridgename, attrname, attrval): + if self._cache_check([bridgename, 'linkinfo', attrname], attrval): + return + self.exec_command('/sbin/brctl set%s %s %s' + %(attrname, bridgename, attrval)) + + def get_bridge_attrs(self, bridgename): + return self._cache_get([bridgename, 'linkinfo']) + + def get_bridgeport_attrs(self, bridgename, bridgeportname): + return self._cache_get([bridgename, 'linkinfo', 'ports', + bridgeportname]) + + def get_bridgeport_attr(self, bridgename, bridgeportname, attrname): + return self._cache_get([bridgename, 'linkinfo', 'ports', + bridgeportname, attrname]) + + def set_stp(self, bridge, stp_state): + self.exec_command('/sbin/brctl stp ' + bridge + ' ' + stp_state) + + def get_stp(self, bridge): + sysfs_stpstate = '/sys/class/net/%s/bridge/stp_state' %bridge + if not os.path.exists(sysfs_stpstate): + return 'error' + stpstate = self.read_file_oneline(sysfs_stpstate) + if not stpstate: + return 'error' + try: + if int(stpstate) > 0: + return 'yes' + elif int(stpstate) == 0: + return 'no' + except: + return 'unknown' + + def conv_value_to_user(self, str): + try: + ret = int(str) / 100 + except: + return None + finally: + return '%d' %ret + + def read_value_from_sysfs(self, filename, preprocess_func): + value = self.read_file_oneline(filename) + if not value: + return None + return preprocess_func(value) + + def set_ageing(self, bridge, ageing): + self.exec_command('/sbin/brctl setageing ' + bridge + ' ' + ageing) + + def get_ageing(self, bridge): + return self.read_value_from_sysfs('/sys/class/net/%s/bridge/ageing_time' + %bridge, self.conv_value_to_user) + + def set_bridgeprio(self, bridge, bridgeprio): + self.exec_command('/sbin/brctl setbridgeprio ' + bridge + ' ' + + bridgeprio) + + def get_bridgeprio(self, bridge): + return self.read_file_oneline( + '/sys/class/net/%s/bridge/priority' %bridge) + + def set_fd(self, bridge, fd): + self.exec_command('/sbin/brctl setfd ' + bridge + ' ' + fd) + + def get_fd(self, bridge): + return self.read_value_from_sysfs( + '/sys/class/net/%s/bridge/forward_delay' + %bridge, self.conv_value_to_user) + + def set_gcint(self, bridge, gcint): + #cmd = '/sbin/brctl setgcint ' + bridge + ' ' + gcint + raise Exception('set_gcint not implemented') + + def set_hello(self, bridge, hello): + self.exec_command('/sbin/brctl sethello ' + bridge + ' ' + hello) + + def get_hello(self, bridge): + return self.read_value_from_sysfs('/sys/class/net/%s/bridge/hello_time' + %bridge, self.conv_value_to_user) + + def set_maxage(self, bridge, maxage): + self.exec_command('/sbin/brctl setmaxage ' + bridge + ' ' + maxage) + + def get_maxage(self, bridge): + return self.read_value_from_sysfs('/sys/class/net/%s/bridge/max_age' + %bridge, self.conv_value_to_user) + + def set_pathcost(self, bridge, port, pathcost): + self.exec_command('/sbin/brctl setpathcost %s' %bridge + ' %s' %port + + ' %s' %pathcost) + + def get_pathcost(self, bridge, port): + return self.read_file_oneline('/sys/class/net/%s/brport/path_cost' + %port) + + def set_portprio(self, bridge, port, prio): + self.exec_command('/sbin/brctl setportprio %s' %bridge + ' %s' %port + + ' %s' %prio) + + def get_portprio(self, bridge, port): + return self.read_file_oneline('/sys/class/net/%s/brport/priority' + %port) + + def set_hashmax(self, bridge, hashmax): + self.exec_command('/sbin/brctl sethashmax %s' %bridge + ' %s' %hashmax) + + def get_hashmax(self, bridge): + return self.read_file_oneline('/sys/class/net/%s/bridge/hash_max' + %bridge) + + def set_hashel(self, bridge, hashel): + self.exec_command('/sbin/brctl sethashel %s' %bridge + ' %s' %hashel) + + def get_hashel(self, bridge): + return self.read_file_oneline('/sys/class/net/%s/bridge/hash_elasticity' + %bridge) + + def set_mclmc(self, bridge, mclmc): + self.exec_command('/sbin/brctl setmclmc %s' %bridge + ' %s' %mclmc) + + def get_mclmc(self, bridge): + return self.read_file_oneline( + '/sys/class/net/%s/bridge/multicast_last_member_count' + %bridge) + + def set_mcrouter(self, bridge, mcrouter): + self.exec_command('/sbin/brctl setmcrouter %s' %bridge + + ' %s' %mcrouter) + + def get_mcrouter(self, bridge): + return self.read_file_oneline( + '/sys/class/net/%s/bridge/multicast_router' %bridge) + + def set_mcsnoop(self, bridge, mcsnoop): + self.exec_command('/sbin/brctl setmcsnoop %s' %bridge + + ' %s' %mcsnoop) + + def get_mcsnoop(self, bridge): + return self.read_file_oneline( + '/sys/class/net/%s/bridge/multicast_snooping' %bridge) + + def set_mcsqc(self, bridge, mcsqc): + self.exec_command('/sbin/brctl setmcsqc %s' %bridge + + ' %s' %mcsqc) + + def get_mcsqc(self, bridge): + return self.read_file_oneline( + '/sys/class/net/%s/bridge/multicast_startup_query_count' + %bridge) + + def set_mcqifaddr(self, bridge, mcqifaddr): + self.exec_command('/sbin/brctl setmcqifaddr %s' %bridge + + ' %s' %mcqifaddr) + + def get_mcqifaddr(self, bridge): + return self.read_file_oneline( + '/sys/class/net/%s/bridge/multicast_startup_query_use_ifaddr' + %bridge) + + def set_mcquerier(self, bridge, mcquerier): + self.exec_command('/sbin/brctl setmcquerier %s' %bridge + + ' %s' %mcquerier) + + def get_mcquerier(self, bridge): + return self.read_file_oneline( + '/sys/class/net/%s/bridge/multicast_querier' %bridge) + + def set_mcqv4src(self, bridge, vlan, mcquerier): + if vlan == 0 or vlan > 4095: + self.logger.warn('mcqv4src vlan \'%d\' invalid range' %vlan) + return + + ip = mcquerier.split('.') + if len(ip) != 4: + self.logger.warn('mcqv4src \'%s\' invalid IPv4 address' %mcquerier) + return + for k in ip: + if not k.isdigit() or int(k, 10) < 0 or int(k, 10) > 255: + self.logger.warn('mcqv4src \'%s\' invalid IPv4 address' %mcquerier) + return + + self.exec_command('/sbin/brctl setmcqv4src %s' %bridge + + ' %d %s' %(vlan, mcquerier)) + + def del_mcqv4src(self, bridge, vlan): + self.exec_command('/sbin/brctl delmcqv4src %s %d' %(bridge, vlan)) + + def get_mcqv4src(self, bridge, vlan=None): + mcqv4src = {} + mcqout = self.exec_command('/sbin/brctl showmcqv4src %s' %bridge) + if not mcqout: return None + mcqlines = mcqout.splitlines() + for l in mcqlines[1:]: + l=l.strip() + k, d, v = l.split('\t') + if not k or not v: + continue + mcqv4src[k] = v + if vlan: + return mcqv4src.get(vlan) + return mcqv4src + + def set_mclmi(self, bridge, mclmi): + self.exec_command('/sbin/brctl setmclmi %s' %bridge + + ' %s' %mclmi) + + def get_mclmi(self, bridge): + return self.read_file_oneline( + '/sys/class/net/%s/bridge/multicast_last_member_interval' + %bridge) + + def set_mcmi(self, bridge, mcmi): + self.exec_command('/sbin/brctl setmcmi %s' %bridge + + ' %s' %mcmi) + + def get_mcmi(self, bridge): + return self.read_file_oneline( + '/sys/class/net/%s/bridge/multicast_membership_interval' + %bridge) + + def bridge_exists(self, bridge): + return os.path.exists('/sys/class/net/%s/bridge' %bridge) + + def is_bridge_port(self, ifacename): + return os.path.exists('/sys/class/net/%s/brport' %ifacename) + + def bridge_port_exists(self, bridge, bridgeportname): + try: + return os.path.exists('/sys/class/net/%s/brif/%s' + %(bridge, bridgeportname)) + except Exception: + return False + + def get_bridge_ports(self, bridgename): + try: + return os.listdir('/sys/class/net/%s/brif/' %bridgename) + except: + return [] diff --git a/ifupdown2/ifupdownaddons/cache.py b/ifupdown2/ifupdownaddons/cache.py new file mode 100644 index 0000000..61773b5 --- /dev/null +++ b/ifupdown2/ifupdownaddons/cache.py @@ -0,0 +1,90 @@ +#!/usr/bin/python +# +# Copyright 2014 Cumulus Networks, Inc. All rights reserved. +# Author: Roopa Prabhu, roopa@cumulusnetworks.com +# + +import pprint +from collections import OrderedDict + +class linkCache(): + """ This class contains methods and instance variables to cache + link info """ + + _shared_state = {} + + """ { : { 'ifindex': , + 'mtu': , + 'state' : ', + 'flags' : , + 'kind' : , + 'linkinfo' : { : , + : , + : { + } """ + links = {} + @classmethod + def get_attr(cls, mapList): + return reduce(lambda d, k: d[k], mapList, linkCache.links) + + @classmethod + def set_attr(cls, mapList, value): + cls.get_attr(mapList[:-1])[mapList[-1]] = value + + @classmethod + def del_attr(cls, mapList): + try: + del cls.get_attr(mapList[:-1])[mapList[-1]] + except: + pass + + @classmethod + def update_attrdict(cls, mapList, valuedict): + try: + cls.get_attr(mapList[:-1])[mapList[-1]].update(valuedict) + except: + cls.get_attr(mapList[:-1])[mapList[-1]] = valuedict + pass + + @classmethod + def append_to_attrlist(cls, mapList, value): + cls.get_attr(mapList[:-1])[mapList[-1]].append(value) + + @classmethod + def remove_from_attrlist(cls, mapList, value): + try: + cls.get_attr(mapList[:-1])[mapList[-1]].remove(value) + except: + pass + + @classmethod + def check_attr(cls, attrlist, value=None): + try: + cachedvalue = cls.get_attr(attrlist) + if value: + if cachedvalue == value: + return True + else: + return False + elif cachedvalue: + return True + else: + return False + except: + return False + + @classmethod + def invalidate(cls): + cls.links = {} + + @classmethod + def dump(cls): + print 'Dumping link cache' + pp = pprint.PrettyPrinter(indent=4) + pp.pprint(cls.links) + + @classmethod + def dump_link(cls, linkname): + print 'Dumping link %s' %linkname + pp = pprint.PrettyPrinter(indent=4) + pp.pprint(cls.links.get(linkname)) diff --git a/ifupdown2/ifupdownaddons/dhclient.py b/ifupdown2/ifupdownaddons/dhclient.py new file mode 100644 index 0000000..811ff40 --- /dev/null +++ b/ifupdown2/ifupdownaddons/dhclient.py @@ -0,0 +1,86 @@ +#!/usr/bin/python +# +# Copyright 2014 Cumulus Networks, Inc. All rights reserved. +# Author: Roopa Prabhu, roopa@cumulusnetworks.com +# + +from utilsbase import * +import subprocess +import os + +FNULL = open(os.devnull, 'w') + +class dhclient(utilsBase): + """ This class contains helper methods to interact with the dhclient + utility """ + + def _pid_exists(self, pidfilename): + if os.path.exists(pidfilename): + pid = self.read_file_oneline(pidfilename) + if not os.path.exists('/proc/%s' %pid): + return False + else: + return False + return True + + def is_running(self, ifacename): + return self._pid_exists('/run/dhclient.%s.pid' %ifacename) + + def is_running6(self, ifacename): + return self._pid_exists('/run/dhclient6.%s.pid' %ifacename) + + def stop(self, ifacename): + if os.path.exists('/sbin/dhclient3'): + cmd = ['/sbin/dhclient3', '-x', '-pf', + '/run/dhclient.%s.pid' %ifacename, '-lf', + '/var/lib/dhcp3/dhclient.%s.leases' %ifacename, + '%s' %ifacename] + else: + cmd = ['/sbin/dhclient', '-x', '-pf', + '/run/dhclient.%s.pid' %ifacename, + '-lf', '/var/lib/dhcp/dhclient.%s.leases' %ifacename, + '%s' %ifacename] + self.subprocess_check_call(cmd) + + def start(self, ifacename): + if os.path.exists('/sbin/dhclient3'): + cmd = ['/sbin/dhclient3', '-pf', + '/run/dhclient.%s.pid' %ifacename, + '-lf', '/var/lib/dhcp3/dhclient.%s.leases' %ifacename, + '%s' %ifacename] + else: + cmd = ['/sbin/dhclient', '-pf', '/run/dhclient.%s.pid' %ifacename, + '-lf', '/var/lib/dhcp/dhclient.%s.leases' %ifacename, + '%s' %ifacename] + self.subprocess_check_call(cmd) + + def release(self, ifacename): + if os.path.exists('/sbin/dhclient3'): + cmd = ['/sbin/dhclient3', '-r', '-pf', + '/run/dhclient.%s.pid' %ifacename, '-lf', + '/var/lib/dhcp3/dhclient.%s.leases' %ifacename, + '%s' %ifacename] + else: + cmd = ['/sbin/dhclient', '-r', '-pf', + '/run/dhclient.%s.pid' %ifacename, + '-lf', '/var/lib/dhcp/dhclient.%s.leases' %ifacename, + '%s' %ifacename] + self.subprocess_check_call(cmd) + + def start6(self, ifacename): + self.subprocess_check_call(['dhclient', '-6', '-pf', + '/run/dhclient6.%s.pid' %ifacename, '-lf', + '/var/lib/dhcp/dhclient.%s.leases ' %ifacename, + '%s' %ifacename]) + + def stop6(self, ifacename): + self.subprocess_check_call(['dhclient', '-6', '-x', '-pf', + '/run/dhclient.%s.pid' %ifacename, '-lf', + '/var/lib/dhcp/dhclient.%s.leases ' %ifacename, + '%s' %ifacename]) + + def release6(self, ifacename): + self.subprocess_check_call(['dhclient', '-6', '-r', '-pf', + '/run/dhclient6.%s.pid' %ifacename, '-lf', + '/var/lib/dhcp/dhclient6.%s.leases' %ifacename, + '%s' %ifacename]) diff --git a/ifupdown2/ifupdownaddons/ifenslaveutil.py b/ifupdown2/ifupdownaddons/ifenslaveutil.py new file mode 100644 index 0000000..c7e0d42 --- /dev/null +++ b/ifupdown2/ifupdownaddons/ifenslaveutil.py @@ -0,0 +1,421 @@ +#!/usr/bin/python +# +# Copyright 2014 Cumulus Networks, Inc. All rights reserved. +# Author: Roopa Prabhu, roopa@cumulusnetworks.com +# + +import os +import re +from ifupdown.iface import * +from utilsbase import * +from iproute2 import * +from cache import * + +class ifenslaveutil(utilsBase): + """ This class contains methods to interact with linux kernel bond + related interfaces """ + + _cache_fill_done = False + + def __init__(self, *args, **kargs): + utilsBase.__init__(self, *args, **kargs) + if self.CACHE and not self._cache_fill_done: + self._bond_linkinfo_fill_all() + self._cache_fill_done = True + + def _bond_linkinfo_fill_attrs(self, bondname): + try: + linkCache.links[bondname]['linkinfo'] = {} + except: + linkCache.links[bondname] = {'linkinfo': {}} + + try: + linkCache.set_attr([bondname, 'linkinfo', 'slaves'], + self.read_file_oneline('/sys/class/net/%s/bonding/slaves' + %bondname).split()) + linkCache.set_attr([bondname, 'linkinfo', 'mode'], + self.read_file_oneline('/sys/class/net/%s/bonding/mode' + %bondname).split()[0]) + linkCache.set_attr([bondname, 'linkinfo', 'xmit_hash_policy'], + self.read_file_oneline( + '/sys/class/net/%s/bonding/xmit_hash_policy' + %bondname).split()[0]) + linkCache.set_attr([bondname, 'linkinfo', 'lacp_rate'], + self.read_file_oneline('/sys/class/net/%s/bonding/lacp_rate' + %bondname).split()[1]) + linkCache.set_attr([bondname, 'linkinfo', 'ad_sys_priority'], + self.read_file_oneline('/sys/class/net/%s/bonding/ad_sys_priority' + %bondname)) + linkCache.set_attr([bondname, 'linkinfo', 'ad_sys_mac_addr'], + self.read_file_oneline('/sys/class/net/%s/bonding/ad_sys_mac_addr' + %bondname)) + map(lambda x: linkCache.set_attr([bondname, 'linkinfo', x], + self.read_file_oneline('/sys/class/net/%s/bonding/%s' + %(bondname, x))), + ['use_carrier', 'miimon', 'min_links', 'num_unsol_na', + 'num_grat_arp', 'lacp_bypass_allow', 'lacp_bypass_period', + 'clag_enable']) + except Exception, e: + pass + + def _bond_linkinfo_fill_all(self): + bondstr = self.read_file_oneline('/sys/class/net/bonding_masters') + if not bondstr: + return + [self._bond_linkinfo_fill_attrs(b) for b in bondstr.split()] + + def _bond_linkinfo_fill(self, bondname, refresh=False): + try: + linkCache.get_attr([bondname, 'linkinfo', 'slaves']) + return + except: + pass + bondstr = self.read_file_oneline('/sys/class/net/bonding_masters') + if (not bondstr or bondname not in bondstr.split()): + raise Exception('bond %s not found' %bondname) + self._bond_linkinfo_fill_attrs(bondname) + + def _cache_get(self, attrlist, refresh=False): + try: + if self.DRYRUN: + return None + if self.CACHE: + if not ifenslaveutil._cache_fill_done: + self._bond_linkinfo_fill_all() + ifenslaveutil._cache_fill_done = True + return linkCache.get_attr(attrlist) + if not refresh: + return linkCache.get_attr(attrlist) + self._bond_linkinfo_fill(attrlist[0], refresh) + return linkCache.get_attr(attrlist) + except Exception, e: + self.logger.debug('_cache_get(%s) : [%s]' + %(str(attrlist), str(e))) + pass + return None + + def _cache_check(self, attrlist, value, refresh=False): + try: + attrvalue = self._cache_get(attrlist, refresh) + if attrvalue and attrvalue == value: + return True + except Exception, e: + self.logger.debug('_cache_check(%s) : [%s]' + %(str(attrlist), str(e))) + pass + return False + + def _cache_update(self, attrlist, value): + if self.DRYRUN: return + try: + if attrlist[-1] == 'slaves': + linkCache.add_to_attrlist(attrlist, value) + return + linkCache.add_attr(attrlist, value) + except: + pass + + def _cache_delete(self, attrlist, value=None): + if self.DRYRUN: return + try: + if attrlist[-1] == 'slaves': + linkCache.remove_from_attrlist(attrlist, value) + return + linkCache.del_attr(attrlist) + except: + pass + + def _cache_invalidate(self): + if self.DRYRUN: return + linkCache.invalidate() + + def set_attrs(self, bondname, attrdict, prehook): + for attrname, attrval in attrdict.items(): + if (self._cache_check([bondname, 'linkinfo', + attrname], attrval)): + continue + if (attrname == 'mode' or attrname == 'xmit_hash_policy' or + attrname == 'lacp_rate' or attrname == 'min_links'): + if prehook: + prehook(bondname) + try: + self.write_file('/sys/class/net/%s/bonding/%s' + %(bondname, attrname), attrval) + except Exception, e: + if self.FORCE: + self.logger.warn(str(e)) + pass + else: + raise + + def set_use_carrier(self, bondname, use_carrier): + if not use_carrier or (use_carrier != '0' and use_carrier != '1'): + return + if (self._cache_check([bondname, 'linkinfo', 'use_carrier'], + use_carrier)): + return + self.write_file('/sys/class/net/%s' %bondname + + '/bonding/use_carrier', use_carrier) + self._cache_update([bondname, 'linkinfo', + 'use_carrier'], use_carrier) + + def get_use_carrier(self, bondname): + return self._cache_get([bondname, 'linkinfo', 'use_carrier']) + + def set_xmit_hash_policy(self, bondname, hash_policy, prehook=None): + valid_values = ['layer2', 'layer3+4', 'layer2+3'] + if not hash_policy: + return + if hash_policy not in valid_values: + raise Exception('invalid hash policy value %s' %hash_policy) + if (self._cache_check([bondname, 'linkinfo', 'xmit_hash_policy'], + hash_policy)): + return + if prehook: + prehook(bondname) + self.write_file('/sys/class/net/%s' %bondname + + '/bonding/xmit_hash_policy', hash_policy) + self._cache_update([bondname, 'linkinfo', 'xmit_hash_policy'], + hash_policy) + + def get_xmit_hash_policy(self, bondname): + return self._cache_get([bondname, 'linkinfo', 'xmit_hash_policy']) + + def set_miimon(self, bondname, miimon): + if (self._cache_check([bondname, 'linkinfo', 'miimon'], + miimon)): + return + self.write_file('/sys/class/net/%s' %bondname + + '/bonding/miimon', miimon) + self._cache_update([bondname, 'linkinfo', 'miimon'], miimon) + + def get_miimon(self, bondname): + return self._cache_get([bondname, 'linkinfo', 'miimon']) + + def set_clag_enable(self, bondname, clag_id): + clag_enable = '0' if clag_id == '0' else '1' + if self._cache_check([bondname, 'linkinfo', 'clag_enable'], + clag_enable) == False: + self.write_file('/sys/class/net/%s' %bondname + + '/bonding/clag_enable', clag_enable) + self._cache_update([bondname, 'linkinfo', 'clag_enable'], + clag_enable) + + def get_clag_enable(self, bondname): + return self._cache_get([bondname, 'linkinfo', 'clag_enable']) + + def set_mode(self, bondname, mode, prehook=None): + valid_modes = ['balance-rr', 'active-backup', 'balance-xor', + 'broadcast', '802.3ad', 'balance-tlb', 'balance-alb'] + if not mode: + return + if mode not in valid_modes: + raise Exception('invalid mode %s' %mode) + if (self._cache_check([bondname, 'linkinfo', 'mode'], + mode)): + return + if prehook: + prehook(bondname) + self.write_file('/sys/class/net/%s' %bondname + '/bonding/mode', mode) + self._cache_update([bondname, 'linkinfo', 'mode'], mode) + + def get_mode(self, bondname): + return self._cache_get([bondname, 'linkinfo', 'mode']) + + def set_lacp_rate(self, bondname, lacp_rate, prehook=None, posthook=None): + if not lacp_rate or (lacp_rate != '0' and lacp_rate != '1'): + return + if (self._cache_check([bondname, 'linkinfo', 'lacp_rate'], + lacp_rate)): + return + if prehook: + prehook(bondname) + try: + self.write_file('/sys/class/net/%s' %bondname + + '/bonding/lacp_rate', lacp_rate) + except: + raise + finally: + if posthook: + prehook(bondname) + self._cache_update([bondname, 'linkinfo', + 'lacp_rate'], lacp_rate) + + def get_lacp_rate(self, bondname): + return self._cache_get([bondname, 'linkinfo', 'lacp_rate']) + + def set_lacp_fallback_allow(self, bondname, allow, prehook=None, posthook=None): + if (self._cache_check([bondname, 'linkinfo', 'lacp_bypass_allow'], + lacp_bypass_allow)): + return + if prehook: + prehook(bondname) + try: + self.write_file('/sys/class/net/%s' %bondname + + '/bonding/lacp_bypass_allow', allow) + except: + raise + finally: + if posthook: + posthook(bondname) + self._cache_update([bondname, 'linkinfo', + 'lacp_bypass_allow'], allow) + + def get_lacp_fallback_allow(self, bondname): + return self._cache_get([bondname, 'linkinfo', 'lacp_bypass_allow']) + + def set_lacp_fallback_period(self, bondname, period, prehook=None, posthook=None): + if (self._cache_check([bondname, 'linkinfo', 'lacp_bypass_period'], + lacp_bypass_period)): + return + if prehook: + prehook(bondname) + try: + self.write_file('/sys/class/net/%s' %bondname + + '/bonding/lacp_bypass_period', period) + except: + raise + finally: + if posthook: + posthook(bondname) + self._cache_update([bondname, 'linkinfo', + 'lacp_bypass_period'], period) + + def get_lacp_fallback_period(self, bondname): + return self._cache_get([bondname, 'linkinfo', 'lacp_bypass_period']) + + def set_min_links(self, bondname, min_links, prehook=None): + if (self._cache_check([bondname, 'linkinfo', 'min_links'], + min_links)): + return + if prehook: + prehook(bondname) + self.write_file('/sys/class/net/%s/bonding/min_links' %bondname, + min_links) + self._cache_update([bondname, 'linkinfo', 'min_links'], min_links) + + def get_min_links(self, bondname): + return self._cache_get([bondname, 'linkinfo', 'min_links']) + + def set_lacp_fallback_priority(self, bondname, port, val): + slavefile = '/sys/class/net/%s/bonding_slave/lacp_bypass_priority' %port + if os.path.exists(slavefile): + self.write_file(slavefile, val) + + def get_lacp_fallback_priority(self, bondname): + slaves = self.get_slaves(bondname) + if not slaves: + return slaves + prios = [] + for slave in slaves: + priofile = '/sys/class/net/%s/bonding_slave/lacp_bypass_priority' %slave + if os.path.exists(priofile): + val = self.read_file_oneline(priofile) + if val and val != '0': + prio = slave + '=' + val + prios.append(prio) + prios.sort() + prio_str = ' '.join(prios) + return prio_str + + def get_ad_sys_mac_addr(self, bondname): + return self._cache_get([bondname, 'linkinfo', 'ad_sys_mac_addr']) + + def get_ad_sys_priority(self, bondname): + return self._cache_get([bondname, 'linkinfo', 'ad_sys_priority']) + + def enslave_slave(self, bondname, slave, prehook=None, posthook=None): + slaves = self._cache_get([bondname, 'linkinfo', 'slaves']) + if slaves and slave in slaves: return + if prehook: + prehook(slave) + self.write_file('/sys/class/net/%s' %bondname + + '/bonding/slaves', '+' + slave) + if posthook: + posthook(slave) + self._cache_update([bondname, 'linkinfo', 'slaves'], slave) + + def remove_slave(self, bondname, slave): + slaves = self._cache_get([bondname, 'linkinfo', 'slaves']) + if slave not in slaves: + return + sysfs_bond_path = ('/sys/class/net/%s' %bondname + + '/bonding/slaves') + if not os.path.exists(sysfs_bond_path): + return + self.write_file(sysfs_bond_path, '-' + slave) + self._cache_delete([bondname, 'linkinfo', 'slaves'], slave) + + def remove_slaves_all(self, bondname): + if not _self._cache_get([bondname, 'linkinfo', 'slaves']): + return + slaves = None + sysfs_bond_path = ('/sys/class/net/%s' %bondname + + '/bonding/slaves') + ipcmd = iproute2() + try: + f = open(sysfs_bond_path, 'r') + slaves = f.readline().strip().split() + f.close() + except IOError, e: + raise Exception('error reading slaves of bond %s' %bondname + + '(' + str(e) + ')') + for slave in slaves: + ipcmd.ip_link_down(slave) + try: + self.remove_slave(bondname, slave) + except Exception, e: + if not self.FORCE: + raise Exception('error removing slave %s' + %slave + ' from bond %s' %bondname + + '(%s)' %str(e)) + else: + pass + self._cache_del([bondname, 'linkinfo', 'slaves']) + + def load_bonding_module(self): + return self.exec_command('modprobe -q bonding') + + def create_bond(self, bondname): + if self.bond_exists(bondname): + return + sysfs_net = '/sys/class/net/' + sysfs_bonding_masters = sysfs_net + 'bonding_masters' + if not os.path.exists(sysfs_bonding_masters): + self.logger.debug('loading bonding driver') + self.load_bonding_module() + return True + self.write_file(sysfs_bonding_masters, '+' + bondname) + self._cache_update([bondname], {}) + + def delete_bond(self, bondname): + if not os.path.exists('/sys/class/net/%s' %bondname): + return + self.write_file('/sys/class/net/bonding_masters', '-' + bondname) + self._cache_delete([bondname]) + + def unset_master(self, bondname): + print 'Do nothing yet' + return 0 + + def get_slaves(self, bondname): + slaves = self._cache_get([bondname, 'linkinfo', 'slaves']) + if slaves: + return list(slaves) + slavefile = '/sys/class/net/%s/bonding/slaves' %bondname + if os.path.exists(slavefile): + buf = self.read_file_oneline(slavefile) + if buf: + slaves = buf.split() + if not slaves: + return slaves + self._cache_update([bondname, 'linkinfo', 'slaves'], slaves) + return list(slaves) + + def bond_slave_exists(self, bond, slave): + slaves = self.get_slaves(bond) + if not slaves: return False + return slave in slaves + + def bond_exists(self, bondname): + return os.path.exists('/sys/class/net/%s/bonding' %bondname) diff --git a/ifupdown2/ifupdownaddons/iproute2.py b/ifupdown2/ifupdownaddons/iproute2.py new file mode 100644 index 0000000..555b12f --- /dev/null +++ b/ifupdown2/ifupdownaddons/iproute2.py @@ -0,0 +1,704 @@ +#!/usr/bin/python +# +# Copyright 2014 Cumulus Networks, Inc. All rights reserved. +# Author: Roopa Prabhu, roopa@cumulusnetworks.com +# + +import os +from collections import OrderedDict +from utilsbase import * +from cache import * + +class iproute2(utilsBase): + """ This class contains helper methods to cache and interact with the + commands in the iproute2 package """ + + _cache_fill_done = False + ipbatchbuf = '' + ipbatch = False + ipbatch_pause = False + + def __init__(self, *args, **kargs): + utilsBase.__init__(self, *args, **kargs) + if self.CACHE and not iproute2._cache_fill_done: + self._link_fill() + self._addr_fill() + iproute2._cache_fill_done = True + + def _link_fill(self, ifacename=None, refresh=False): + """ fills cache with link information + + if ifacename argument given, fill cache for ifacename, else + fill cache for all interfaces in the system + """ + + linkout = {} + if iproute2._cache_fill_done and not refresh: return + try: + # if ifacename already present, return + if (ifacename and not refresh and + linkCache.get_attr([ifacename, 'ifflag'])): + return + except: + pass + cmdout = self.link_show(ifacename=ifacename) + if not cmdout: + return + for c in cmdout.splitlines(): + citems = c.split() + ifnamenlink = citems[1].split('@') + if len(ifnamenlink) > 1: + ifname = ifnamenlink[0] + iflink = ifnamenlink[1].strip(':') + else: + ifname = ifnamenlink[0].strip(':') + iflink = None + linkattrs = {} + linkattrs['link'] = iflink + linkattrs['ifindex'] = citems[0].strip(':') + flags = citems[2].strip('<>').split(',') + linkattrs['flags'] = flags + linkattrs['ifflag'] = 'UP' if 'UP' in flags else 'DOWN' + for i in range(0, len(citems)): + if citems[i] == 'mtu': linkattrs['mtu'] = citems[i+1] + elif citems[i] == 'state': linkattrs['state'] = citems[i+1] + elif citems[i] == 'link/ether': linkattrs['hwaddress'] = citems[i+1] + elif citems[i] == 'vlan' and citems[i+1] == 'id': + linkattrs['linkinfo'] = {'vlanid' : citems[i+2]} + elif citems[i] == 'vxlan' and citems[i+1] == 'id': + vattrs = {'vxlanid' : citems[i+2], + 'svcnode' : [], + 'learning': 'on'} + for j in range(i+2, len(citems)): + if citems[j] == 'local': + vattrs['local'] = citems[j+1] + elif citems[j] == 'svcnode': + vattrs['svcnode'].append(citems[j+1]) + elif citems[j] == 'peernode': + vattrs['peernode'].append(citems[j+1]) + elif citems[j] == 'nolearning': + vattrs['learning'] = 'off' + linkattrs['linkinfo'] = vattrs + break + #linkattrs['alias'] = self.read_file_oneline( + # '/sys/class/net/%s/ifalias' %ifname) + linkout[ifname] = linkattrs + [linkCache.update_attrdict([ifname], linkattrs) + for ifname, linkattrs in linkout.items()] + + def _addr_filter(self, addr, scope=None): + default_addrs = ['127.0.0.1/8', '::1/128' , '0.0.0.0'] + if addr in default_addrs: + return True + if scope and scope == 'link': + return True + return False + + def _addr_fill(self, ifacename=None, refresh=False): + """ fills cache with address information + + if ifacename argument given, fill cache for ifacename, else + fill cache for all interfaces in the system + """ + + linkout = {} + if iproute2._cache_fill_done: return + try: + # Check if ifacename is already full, in which case, return + if ifacename: + linkCache.get_attr([ifacename, 'addrs']) + return + except: + pass + cmdout = self.addr_show(ifacename=ifacename) + if not cmdout: + return + for c in cmdout.splitlines(): + citems = c.split() + ifnamenlink = citems[1].split('@') + if len(ifnamenlink) > 1: + ifname = ifnamenlink[0] + else: + ifname = ifnamenlink[0].strip(':') + if citems[2] == 'inet': + if self._addr_filter(citems[3], scope=citems[5]): + continue + addrattrs = {} + addrattrs['scope'] = citems[5] + addrattrs['type'] = 'inet' + linkout[ifname]['addrs'][citems[3]] = addrattrs + elif citems[2] == 'inet6': + if self._addr_filter(citems[3], scope=citems[5]): + continue + if citems[5] == 'link': continue #skip 'link' addresses + addrattrs = {} + addrattrs['scope'] = citems[5] + addrattrs['type'] = 'inet6' + linkout[ifname]['addrs'][citems[3]] = addrattrs + else: + linkattrs = {} + linkattrs['addrs'] = OrderedDict({}) + try: + linkout[ifname].update(linkattrs) + except KeyError: + linkout[ifname] = linkattrs + + [linkCache.update_attrdict([ifname], linkattrs) + for ifname, linkattrs in linkout.items()] + + def _cache_get(self, type, attrlist, refresh=False): + try: + if self.DRYRUN: + return False + if self.CACHE: + if not iproute2._cache_fill_done: + self._link_fill() + self._addr_fill() + iproute2._cache_fill_done = True + return linkCache.get_attr(attrlist) + if not refresh: + return linkCache.get_attr(attrlist) + if type == 'link': + self._link_fill(attrlist[0], refresh) + elif type == 'addr': + self._addr_fill(attrlist[0], refresh) + else: + self._link_fill(attrlist[0], refresh) + self._addr_fill(attrlist[0], refresh) + return linkCache.get_attr(attrlist) + except Exception, e: + self.logger.debug('_cache_get(%s) : [%s]' + %(str(attrlist), str(e))) + pass + return None + + def _cache_check(self, type, attrlist, value, refresh=False): + try: + attrvalue = self._cache_get(type, attrlist, refresh) + if attrvalue and attrvalue == value: + return True + except Exception, e: + self.logger.debug('_cache_check(%s) : [%s]' + %(str(attrlist), str(e))) + pass + return False + + def _cache_update(self, attrlist, value): + if self.DRYRUN: return + try: + linkCache.add_attr(attrlist, value) + except: + pass + + def _cache_delete(self, attrlist): + if self.DRYRUN: return + try: + linkCache.del_attr(attrlist) + except: + pass + + def _cache_invalidate(self): + linkCache.invalidate() + + def batch_start(self): + self.ipbatcbuf = '' + self.ipbatch = True + self.ipbatch_pause = False + + def add_to_batch(self, cmd): + self.ipbatchbuf += cmd + '\n' + + def batch_pause(self): + self.ipbatch_pause = True + + def batch_resume(self): + self.ipbatch_pause = False + + def batch_commit(self): + if not self.ipbatchbuf: + return + try: + self.exec_command_talk_stdin('ip -force -batch -', + stdinbuf=self.ipbatchbuf) + except Exception: + raise + finally: + self.ipbatchbuf = '' + self.ipbatch = False + self.ipbatch_pause = False + + def addr_show(self, ifacename=None): + if ifacename: + if not self.link_exists(ifacename): + return + return self.exec_commandl(['ip','-o', 'addr', 'show', 'dev', + '%s' %ifacename]) + else: + return self.exec_commandl(['ip', '-o', 'addr', 'show']) + + def link_show(self, ifacename=None): + if ifacename: + return self.exec_commandl(['ip', '-o', '-d', 'link', + 'show', 'dev', '%s' %ifacename]) + else: + return self.exec_commandl(['ip', '-o', '-d', 'link', 'show']) + + def addr_add(self, ifacename, address, broadcast=None, + peer=None, scope=None, preferred_lifetime=None): + if not address: + return + cmd = 'addr add %s' %address + if broadcast: + cmd += ' broadcast %s' %broadcast + if peer: + cmd += ' peer %s' %peer + if scope: + cmd += ' scope %s' %scope + if preferred_lifetime: + cmd += ' preferred_lft %s' %preferred_lifetime + cmd += ' dev %s' %ifacename + if self.ipbatch and not self.ipbatch_pause: + self.add_to_batch(cmd) + else: + self.exec_command('ip ' + cmd) + self._cache_update([ifacename, 'addrs', address], {}) + + def addr_del(self, ifacename, address, broadcast=None, + peer=None, scope=None): + """ Delete ipv4 address """ + if not address: + return + if not self._cache_get('addr', [ifacename, 'addrs', address]): + return + cmd = 'addr del %s' %address + if broadcast: + cmd += 'broadcast %s' %broadcast + if peer: + cmd += 'peer %s' %peer + if scope: + cmd += 'scope %s' %scope + cmd += ' dev %s' %ifacename + self.exec_command('ip ' + cmd) + self._cache_delete([ifacename, 'addrs', address]) + + def addr_flush(self, ifacename): + cmd = 'addr flush dev %s' %ifacename + if self.ipbatch and not self.ipbatch_pause: + self.add_to_batch(cmd) + else: + self.exec_command('ip ' + cmd) + self._cache_delete([ifacename, 'addrs']) + + def del_addr_all(self, ifacename, skip_addrs=[]): + if not skip_addrs: skip_addrs = [] + runningaddrsdict = self.addr_get(ifacename) + try: + # XXX: ignore errors. Fix this to delete secondary addresses + # first + [self.addr_del(ifacename, a) for a in + set(runningaddrsdict.keys()).difference(skip_addrs)] + except: + # ignore errors + pass + + def addr_get(self, ifacename, details=True): + addrs = self._cache_get('addr', [ifacename, 'addrs']) + if not addrs: + return None + if details: + return addrs + return addrs.keys() + + def addr_add_multiple(self, ifacename, addrs, purge_existing=False): + # purges address + if purge_existing: + # if perfmode is not set and also if iface has no sibling + # objects, purge addresses that are not present in the new + # config + runningaddrs = self.addr_get(ifacename, details=False) + if addrs == runningaddrs: + return + try: + # if primary address is not same, there is no need to keep any. + # reset all addresses + if (addrs and runningaddrs and + (addrs[0] != runningaddrs[0])): + self.del_addr_all(ifacename) + else: + self.del_addr_all(ifacename, addrs) + except Exception, e: + self.log_warn(str(e)) + for a in addrs: + try: + self.addr_add(ifacename, a) + except Exception, e: + self.logger.error(str(e)) + + def _link_set_ifflag(self, ifacename, value): + # Dont look at the cache, the cache may have stale value + # because link status can be changed by external + # entity (One such entity is ifupdown main program) + cmd = 'link set dev %s %s' %(ifacename, value.lower()) + if self.ipbatch: + self.add_to_batch(cmd) + else: + self.exec_command('ip ' + cmd) + + def link_up(self, ifacename): + self._link_set_ifflag(ifacename, 'UP') + + def link_down(self, ifacename): + self._link_set_ifflag(ifacename, 'DOWN') + + def link_set(self, ifacename, key, value=None, force=False): + if not force: + if (key not in ['master', 'nomaster'] and + self._cache_check('link', [ifacename, key], value)): + return + cmd = 'link set dev %s %s' %(ifacename, key) + if value: + cmd += ' %s' %value + if self.ipbatch: + self.add_to_batch(cmd) + else: + self.exec_command('ip ' + cmd) + if key not in ['master', 'nomaster']: + self._cache_update([ifacename, key], value) + + def link_set_hwaddress(self, ifacename, hwaddress, force=False): + if not force: + if self._cache_check('link', [ifacename, 'hwaddress'], hwaddress): + return + self.link_down(ifacename) + cmd = 'link set dev %s address %s' %(ifacename, hwaddress) + if self.ipbatch: + self.add_to_batch(cmd) + else: + self.exec_command('ip ' + cmd) + self.link_up(ifacename) + self._cache_update([ifacename, 'hwaddress'], hwaddress) + + def link_set_alias(self, ifacename, alias): + self.exec_commandl(['ip', 'link', 'set', 'dev', + ifacename, 'alias', alias]) + + def link_get_alias(self, ifacename): + return self.read_file_oneline('/sys/class/net/%s/ifalias' + %ifacename) + + def link_isloopback(self, ifacename): + flags = self._cache_get('link', [ifacename, 'flags']) + if not flags: + return + if 'LOOPBACK' in flags: + return True + return False + + def link_get_status(self, ifacename): + return self._cache_get('link', [ifacename, 'ifflag'], refresh=True) + + def route_add_gateway(self, ifacename, gateway, metric=None): + if not gateway: + return + cmd = 'ip route add default via %s' %gateway + # Add metric + if metric: + cmd += 'metric %s' %metric + cmd += ' dev %s' %ifacename + self.exec_command(cmd) + + def route_del_gateway(self, ifacename, gateway, metric=None): + # delete default gw + if not gateway: + return + cmd = 'ip route del default via %s' %gateway + if metric: + cmd += ' metric %s' %metric + cmd += ' dev %s' %ifacename + self.exec_command(cmd) + + def route6_add_gateway(self, ifacename, gateway): + if not gateway: + return + return self.exec_command('ip -6 route add default via %s' %gateway + + ' dev %s' %ifacename) + + def route6_del_gateway(self, ifacename, gateway): + if not gateway: + return + return self.exec_command('ip -6 route del default via %s' %gateway + + 'dev %s' %ifacename) + + def link_create_vlan(self, vlan_device_name, vlan_raw_device, vlanid): + if self.link_exists(vlan_device_name): + return + self.exec_command('ip link add link %s' %vlan_raw_device + + ' name %s' %vlan_device_name + + ' type vlan id %d' %vlanid) + self._cache_update([vlan_device_name], {}) + + def link_create_vlan_from_name(self, vlan_device_name): + v = vlan_device_name.split('.') + if len(v) != 2: + self.logger.warn('invalid vlan device name %s' %vlan_device_name) + return + self.link_create_vlan(vlan_device_name, v[0], v[1]) + + def link_create_macvlan(self, name, linkdev, mode='private'): + if self.link_exists(name): + return + cmd = ('link add link %s' %linkdev + + ' name %s' %name + + ' type macvlan mode %s' %mode) + if self.ipbatch and not self.ipbatch_pause: + self.add_to_batch(cmd) + else: + self.exec_command('ip %s' %cmd) + self._cache_update([name], {}) + + def link_create_vxlan(self, name, vxlanid, + localtunnelip=None, + svcnodeips=None, + peernodeips=None, + learning='on'): + if svcnodeips and peernodeips: + raise Exception("svcnodeip and peernodeip is mutually exclusive") + args = '' + if localtunnelip: + args += ' local %s' %localtunnelip + if svcnodeips: + for s in svcnodeips: + args += ' svcnode %s' %s + if peernodeips: + for s in peernodeips: + args += ' peernode %s' %s + if learning == 'off': + args += ' nolearning' + + if self.link_exists(name): + cmd = 'link set dev %s type vxlan ' %(name) + else: + cmd = 'link add dev %s type vxlan id %s' %(name, vxlanid) + cmd += args + + if self.ipbatch and not self.ipbatch_pause: + self.add_to_batch(cmd) + else: + self.exec_command('ip %s' %cmd) + # XXX: update linkinfo correctly + self._cache_update([name], {}) + + def link_exists(self, ifacename): + if self.DRYRUN: + return True + return os.path.exists('/sys/class/net/%s' %ifacename) + + def is_vlan_device_by_name(self, ifacename): + if re.search(r'\.', ifacename): + return True + return False + + def route_add(self, route): + self.exec_command('ip route add ' + route) + + def route6_add(self, route): + self.exec_command('ip -6 route add ' + route) + + def get_vlandev_attrs(self, ifacename): + return (self._cache_get('link', [ifacename, 'linkinfo', 'link']), + self._cache_get('link', [ifacename, 'linkinfo', 'vlanid'])) + + def get_vxlandev_attrs(self, ifacename): + return self._cache_get('link', [ifacename, 'linkinfo']) + + def link_get_mtu(self, ifacename): + return self._cache_get('link', [ifacename, 'mtu']) + + def link_get_hwaddress(self, ifacename): + address = self._cache_get('link', [ifacename, 'hwaddress']) + # newly created logical interface addresses dont end up in the cache + # read hwaddress from sysfs file for these interfaces + if not address: + address = self.read_file_oneline('/sys/class/net/%s/address' + %ifacename) + return address + + def link_create(self, ifacename, type, link=None): + if self.link_exists(ifacename): + return + cmd = 'link add' + if link: + cmd += ' link %s' %link + cmd += ' name %s type %s' %(ifacename, type) + if self.ipbatch and not self.ipbatch_pause: + self.add_to_batch(cmd) + else: + self.exec_command('ip %s' %cmd) + self._cache_update([ifacename], {}) + + def link_delete(self, ifacename): + if not self.link_exists(ifacename): + return + cmd = 'link del %s' %ifacename + if self.ipbatch and not self.ipbatch_pause: + self.add_to_batch(cmd) + else: + self.exec_command('ip %s' %cmd) + self._cache_invalidate() + + def bridge_port_vids_add(self, bridgeportname, vids): + [self.exec_command('bridge vlan add vid %s dev %s' + %(v, bridgeportname)) for v in vids] + + def bridge_port_vids_del(self, bridgeportname, vids): + if not vids: + return + [self.exec_command('bridge vlan del vid %s dev %s' + %(v, bridgeportname)) for v in vids] + + def bridge_port_vids_flush(self, bridgeportname): + self.exec_command('bridge vlan del vid %s dev %s' + %(vid, bridgeportname)) + + def bridge_port_vids_get(self, bridgeportname): + self.exec_command('/bin/bridge vlan show %s' %bridgeportname) + bridgeout = self.exec_command('/bin/bridge vlan show dev %s' + %bridgeportname) + if not bridgeout: return [] + brvlanlines = bridgeout.readlines()[2:] + vids = [l.strip() for l in brvlanlines] + return [vid for v in vids if vid] + + def bridge_port_vids_get_all(self): + brvlaninfo = {} + bridgeout = self.exec_command('/bin/bridge vlan show') + if not bridgeout: return brvlaninfo + brvlanlines = bridgeout.splitlines() + brportname=None + for l in brvlanlines[1:]: + if l and l[0] not in [' ', '\t']: + brportname = None + l=l.strip() + if not l: + brportname=None + continue + if 'PVID' in l: + attrs = l.split() + brportname = attrs[0] + brvlaninfo[brportname] = {'pvid' : attrs[1], + 'vlan' : []} + elif brportname: + if 'Egress Untagged' not in l: + brvlaninfo[brportname]['vlan'].append(l) + elif not brportname: + attrs = l.split() + if attrs[1] == 'None' or 'Egress Untagged' in attrs[1]: + continue + brportname = attrs[0] + brvlaninfo[brportname] = {'vlan' : [attrs[1]]} + return brvlaninfo + + def bridge_port_pvid_add(self, bridgeportname, pvid): + self.exec_command('bridge vlan add vid %s untagged pvid dev %s' + %(pvid, bridgeportname)) + + def bridge_port_pvid_del(self, bridgeportname, pvid): + self.exec_command('bridge vlan del vid %s untagged pvid dev %s' + %(pvid, bridgeportname)) + + def bridge_port_pvids_get(self, bridgeportname): + return self.read_file_oneline('/sys/class/net/%s/brport/pvid' + %bridgeportname) + + def bridge_vids_add(self, bridgeportname, vids, bridge=True): + target = 'self' if bridge else '' + [self.exec_command('bridge vlan add vid %s dev %s %s' + %(v, bridgeportname, target)) for v in vids] + + def bridge_vids_del(self, bridgeportname, vids, bridge=True): + target = 'self' if bridge else '' + [self.exec_command('bridge vlan del vid %s dev %s %s' + %(v, bridgeportname, target)) for v in vids] + + def bridge_fdb_add(self, dev, address, vlan=None, bridge=True): + target = 'self' if bridge else '' + if vlan: + self.exec_command('bridge fdb replace %s dev %s vlan %s %s' + %(address, dev, vlan, target)) + else: + self.exec_command('bridge fdb replace %s dev %s %s' + %(address, dev, target)) + + def bridge_fdb_del(self, dev, address, vlan=None, bridge=True): + target = 'self' if bridge else '' + if vlan: + self.exec_command('bridge fdb del %s dev %s vlan %s %s' + %(address, dev, vlan, target)) + else: + self.exec_command('bridge fdb del %s dev %s %s' + %(address, dev, target)) + + def bridge_is_vlan_aware(self, bridgename): + filename = '/sys/class/net/%s/bridge/vlan_filtering' %bridgename + if os.path.exists(filename) and self.read_file_oneline(filename) == '1': + return True + return False + + def bridge_port_get_bridge_name(self, bridgeport): + filename = '/sys/class/net/%s/brport/bridge' %bridgeport + try: + return os.path.basename(os.readlink(filename)) + except: + return None + + def bridge_port_exists(self, bridge, bridgeportname): + try: + return os.path.exists('/sys/class/net/%s/brif/%s' + %(bridge, bridgeportname)) + except Exception: + return False + + def bridge_fdb_show_dev(self, dev): + try: + fdbs = {} + output = self.exec_command('bridge fdb show dev %s' %dev) + if output: + for fdb_entry in output.splitlines(): + try: + entries = fdb_entry.split() + fdbs.setdefault(entries[2], []).append(entries[0]) + except: + self.logger.debug('%s: invalid fdb line \'%s\'' + %(dev, fdb_entry)) + pass + return fdbs + except Exception: + return None + + def is_bridge(self, bridge): + return os.path.exists('/sys/class/net/%s/bridge' %bridge) + + def is_link_up(self, ifacename): + ret = False + try: + flags = self.read_file_oneline('/sys/class/net/%s/flags' %ifacename) + iflags = int(flags, 16) + if (iflags & 0x0001): + ret = True + except: + ret = False + pass + return ret + + def ip_route_get_dev(self, prefix): + try: + output = self.exec_command('ip route get %s' %prefix) + if output: + rline = output.splitlines()[0] + if rline: + rattrs = rline.split() + return rattrs[rattrs.index('dev') + 1] + except Exception, e: + self.logger.debug('ip_route_get_dev: failed .. %s' %str(e)) + pass + return None diff --git a/ifupdown2/ifupdownaddons/modulebase.py b/ifupdown2/ifupdownaddons/modulebase.py new file mode 100644 index 0000000..47df790 --- /dev/null +++ b/ifupdown2/ifupdownaddons/modulebase.py @@ -0,0 +1,363 @@ +#!/usr/bin/python +# +# Copyright 2014 Cumulus Networks, Inc. All rights reserved. +# Author: Roopa Prabhu, roopa@cumulusnetworks.com +# + +import os +import re +import io +import logging +import subprocess +import traceback +from ifupdown.iface import * +#from ifupdownaddons.iproute2 import * +#from ifupdownaddons.dhclient import * +#from ifupdownaddons.bridgeutils import * +#from ifupdownaddons.mstpctlutil import * +#from ifupdownaddons.ifenslaveutil import * + +class moduleBase(object): + """ Base class for ifupdown addon modules + + Provides common infrastructure methods for all addon modules """ + + def __init__(self, *args, **kargs): + modulename = self.__class__.__name__ + self.logger = logging.getLogger('ifupdown.' + modulename) + self.FORCE = kargs.get('force', False) + """force interface configuration""" + self.DRYRUN = kargs.get('dryrun', False) + """only predend you are applying configuration, dont really do it""" + self.NOWAIT = kargs.get('nowait', False) + self.PERFMODE = kargs.get('perfmode', False) + self.CACHE = kargs.get('cache', False) + self.CACHE_FLAGS = kargs.get('cacheflags', 0x0) + + def log_warn(self, str): + """ log a warning if err str is not one of which we should ignore """ + if not self.ignore_error(str): + if self.logger.getEffectiveLevel() == logging.DEBUG: + traceback.print_stack() + self.logger.warn(str) + pass + + def log_error(self, str): + """ log an err if err str is not one of which we should ignore and raise an exception """ + if not self.ignore_error(str): + if self.logger.getEffectiveLevel() == logging.DEBUG: + traceback.print_stack() + raise Exception(str) + else: + pass + + def is_process_running(self, procName): + try: + self.exec_command('/bin/pidof -x %s' % procName) + except: + return False + else: + return True + + def exec_command(self, cmd, cmdenv=None): + """ execute command passed as argument. + + Args: + cmd (str): command to execute + + Kwargs: + cmdenv (dict): environment variable name value pairs + """ + 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, e: + raise Exception('could not execute ' + cmd + + '(' + str(e) + ')') + if cmd_returncode != 0: + raise Exception('error executing cmd \'%s\'' %cmd + + '(' + cmdout.strip('\n ') + ')') + return cmdout + + def exec_command_talk_stdin(self, cmd, stdinbuf): + """ execute command passed as argument and write contents of stdinbuf + into stdin of the cmd + + Args: + cmd (str): command to execute + stdinbuf (str): string to write to stdin of the cmd process + """ + cmd_returncode = 0 + cmdout = '' + + try: + self.logger.info('Executing %s (stdin=%s)' %(cmd, stdinbuf)) + if self.DRYRUN: + return cmdout + ch = subprocess.Popen(cmd.split(), + stdout=subprocess.PIPE, + stdin=subprocess.PIPE, + shell=False, env=cmdenv, + stderr=subprocess.STDOUT, + close_fds=True) + cmdout = ch.communicate(input=stdinbuf)[0] + cmd_returncode = ch.wait() + except OSError, e: + raise Exception('could not execute ' + cmd + + '(' + str(e) + ')') + if cmd_returncode != 0: + raise Exception('error executing cmd \'%s (%s)\'' + %(cmd, stdinbuf) + '(' + cmdout.strip('\n ') + ')') + return cmdout + + def get_ifaces_from_proc(self): + ifacenames = [] + with open('/proc/net/dev') as f: + try: + lines = f.readlines() + for line in lines: + ifacenames.append(line.split()[0].strip(': ')) + except: + raise + return ifacenames + + def parse_regex(self, expr, ifacenames=None): + try: + proc_ifacenames = self.get_ifaces_from_proc() + except: + self.logger.warn('error reading ifaces from proc') + for proc_ifacename in proc_ifacenames: + if re.search(expr + '$', proc_ifacename): + yield proc_ifacename + if not ifacenames: + return + for ifacename in ifacenames: + if re.search(expr + '$', ifacename): + yield ifacename + + def parse_glob(self, expr): + errmsg = ('error parsing glob expression \'%s\'' %expr + + ' (supported glob syntax: swp1-10 or swp[1-10])') + start_index = 0 + end_index = 0 + try: + regexs = [re.compile(r"([A-Za-z0-9\-]+[A-Za-z])(\d+)\-(\d+)(.*)"), + re.compile(r"([A-Za-z0-9\-]+)\[(\d+)\-(\d+)\](.*)")] + for r in regexs: + m = r.match(expr) + if not m: + continue + mlist = m.groups() + if len(mlist) != 4: + raise Exception(errmsg + '(unexpected len)') + prefix = mlist[0] + suffix = mlist[3] + start_index = int(mlist[1]) + end_index = int(mlist[2]) + except: + self.logger.warn(errmsg) + pass + if not start_index and not end_index: + self.logger.warn(errmsg) + yield expr + else: + for i in range(start_index, end_index + 1): + yield prefix + '%d' %i + suffix + + def parse_port_list(self, port_expr, ifacenames=None): + """ parse port list containing glob and regex + + Args: + port_expr (str): expression + ifacenames (list): list of interface names. This needs to be specified if the expression has a regular expression + """ + regex = 0 + glob = 0 + portlist = [] + + if not port_expr: + return None + for expr in re.split(r'[\s\t]\s*', port_expr): + if expr == 'noregex': + regex = 0 + elif expr == 'noglob': + glob = 0 + elif expr == 'regex': + regex = 1 + elif expr == 'glob': + glob = 1 + elif regex: + for port in self.parse_regex(expr, ifacenames): + if port not in portlist: + portlist.append(port) + regex = 0 + elif glob: + for port in self.parse_glob(expr): + portlist.append(port) + glob = 0 + else: + portlist.append(expr) + if not portlist: + return None + return portlist + + def ignore_error(self, errmsg): + if (self.FORCE or re.search(r'exists', errmsg, + re.IGNORECASE | re.MULTILINE)): + return True + return False + + def write_file(self, filename, strexpr): + """ writes string to a file """ + try: + self.logger.info('writing \'%s\'' %strexpr + + ' to file %s' %filename) + if self.DRYRUN: + return 0 + with open(filename, 'w') as f: + f.write(strexpr) + except IOError, e: + self.logger.warn('error writing to file %s' + %filename + '(' + str(e) + ')') + return -1 + return 0 + + def read_file(self, filename): + """ read file and return lines from the file """ + try: + self.logger.info('reading \'%s\'' %filename) + with open(filename, 'r') as f: + return f.readlines() + except: + return None + return None + + def read_file_oneline(self, filename): + """ reads and returns first line from the file """ + try: + self.logger.info('reading \'%s\'' %filename) + with open(filename, 'r') as f: + return f.readline().strip('\n') + except: + return None + return None + + def sysctl_set(self, variable, value): + """ set sysctl variable to value passed as argument """ + self.exec_command('sysctl %s=' %variable + '%s' %value) + + def sysctl_get(self, variable): + """ get value of sysctl variable """ + return self.exec_command('sysctl %s' %variable).split('=')[1].strip() + + def set_iface_attr(self, ifaceobj, attr_name, attr_valsetfunc, + prehook=None, prehookargs=None): + ifacename = ifaceobj.name + attrvalue = ifaceobj.get_attr_value_first(attr_name) + if attrvalue: + if prehook: + if prehookargs: + prehook(prehookargs) + else: + prehook(ifacename) + attr_valsetfunc(ifacename, attrvalue) + + def query_n_update_ifaceobjcurr_attr(self, ifaceobj, ifaceobjcurr, + attr_name, attr_valgetfunc, + attr_valgetextraarg=None): + attrvalue = ifaceobj.get_attr_value_first(attr_name) + if not attrvalue: + return + if attr_valgetextraarg: + runningattrvalue = attr_valgetfunc(ifaceobj.name, + attr_valgetextraarg) + else: + runningattrvalue = attr_valgetfunc(ifaceobj.name) + if (not runningattrvalue or + (runningattrvalue != attrvalue)): + ifaceobjcurr.update_config_with_status(attr_name, + runningattrvalue, 1) + else: + ifaceobjcurr.update_config_with_status(attr_name, + runningattrvalue, 0) + + def dict_key_subset(self, a, b): + """ returns a list of differing keys """ + return [x for x in a if x in b] + + def get_mod_attrs(self): + """ returns list of all module attrs defined in the module _modinfo dict""" + try: + return self._modinfo.get('attrs').keys() + except: + return None + + def get_mod_attr(self, attrname): + """ returns module attr info """ + try: + return self._modinfo.get('attrs', {}).get(attrname) + except: + return None + + def get_mod_subattr(self, attrname, subattrname): + """ returns module attrs defined in the module _modinfo dict""" + try: + return reduce(lambda d, k: d[k], ['attrs', attrname, subattrname], + self._modinfo) + except: + return None + + def get_modinfo(self): + """ return module info """ + try: + return self._modinfo + except: + return None + + def get_flags(self): + return dict(force=self.FORCE, dryrun=self.DRYRUN, nowait=self.NOWAIT, + perfmode=self.PERFMODE, cache=self.CACHE, + cacheflags=self.CACHE_FLAGS) + + def _get_reserved_vlan_range(self): + start = end = 0 + get_resvvlan = '/usr/share/python-ifupdown2/get_reserved_vlan_range.sh' + if not os.path.exists(get_resvvlan): + return (start, end) + try: + (s, e) = self.exec_command(get_resvvlan).strip('\n').split('-') + start = int(s) + end = int(e) + except Exception, e: + self.logger.debug('%s failed (%s)' %(get_resvvlan, str(e))) + # ignore errors + pass + return (start, end) + + def _handle_reserved_vlan(self, vlanid, logprefix=''): + """ Helper function to check and warn if the vlanid falls in the + reserved vlan range """ + if vlanid in range(self._resv_vlan_range[0], + self._resv_vlan_range[1]): + self.logger.error('%s: reserved vlan %d being used' + %(logprefix, vlanid) + ' (reserved vlan range %d-%d)' + %(self._resv_vlan_range[0], self._resv_vlan_range[1])) + return True + return False + + def _valid_ethaddr(self, ethaddr): + """ Check if address is 00:00:00:00:00:00 """ + if not ethaddr or re.match('00:00:00:00:00:00', ethaddr): + return False + return True diff --git a/ifupdown2/ifupdownaddons/mstpctlutil.py b/ifupdown2/ifupdownaddons/mstpctlutil.py new file mode 100644 index 0000000..4716195 --- /dev/null +++ b/ifupdown2/ifupdownaddons/mstpctlutil.py @@ -0,0 +1,171 @@ +#!/usr/bin/python +# +# Copyright 2014 Cumulus Networks, Inc. All rights reserved. +# Author: Roopa Prabhu, roopa@cumulusnetworks.com +# + +from utilsbase import * +from ifupdown.iface import * +from cache import * +import re + +class mstpctlutil(utilsBase): + """ This class contains helper methods to interact with mstpd using + mstputils commands """ + + _cache_fill_done = False + + _bridgeattrmap = {'bridgeid' : 'bridge-id', + 'maxage' : 'max-age', + 'fdelay' : 'forward-delay', + 'txholdcount' : 'tx-hold-count', + 'maxhops' : 'max-hops', + 'ageing' : 'ageing-time', + 'hello' : 'hello-time', + 'forcevers' : 'force-protocol-version'} + + _bridgeportattrmap = {'portadminedge' : 'admin-edge-port', + 'portp2p' : 'admin-point-to-point', + 'portrestrrole' : 'restricted-role', + 'portrestrtcn' : 'restricted-TCN', + 'bpduguard' : 'bpdu-guard-port', + 'portautoedge' : 'auto-edge-port', + 'portnetwork' : 'network-port', + 'portbpdufilter' : 'bpdufilter-port'} + + def __init__(self, *args, **kargs): + utilsBase.__init__(self, *args, **kargs) + + def is_mstpd_running(self): + try: + self.exec_command('/bin/pidof mstpd') + except: + return False + else: + return True + + def get_bridgeport_attr(self, bridgename, portname, attrname): + try: + return self.subprocess_check_output(['/sbin/mstpctl', + 'showportdetail', '%s' %bridgename, '%s' %portname, + self._bridgeportattrmap[attrname]]).strip('\n') + except Exception, e: + pass + return None + + def get_bridgeport_attrs(self, bridgename, portname): + bridgeattrs = {} + try: + bridgeattrs = dict((k, self.get_bridgeport_attr(bridgename, v)) + for k, v in self._bridgeattrmap.items()) + bridgeattrs['treeprio'] = int(bridgeattrs.get('bridgeid', + '').split('.')[0], base=16) * 4096 + except Exception, e: + self.logger.warn(str(e)) + pass + return bridgeattrs + + def set_bridgeport_attrs(self, bridgename, bridgeportname, attrdict, + check=True): + for k, v in attrdict.iteritems(): + if not v: + continue + try: + self.set_bridgeport_attr(self, bridgename, bridgeportname, + k, v, check) + except Exception, e: + self.logger.warn(str(e)) + + def set_bridgeport_attr(self, bridgename, bridgeportname, attrname, + attrvalue, check=True): + if check: + attrvalue_curr = self.get_bridgeport_attr(bridgename, + bridgeportname, attrname) + if attrvalue_curr and attrvalue_curr == attrvalue: + return + if attrname == 'treeportcost' or attrname == 'treeportprio': + self.subprocess_check_output(['/sbin/mstpctl', 'set%s' %attrname, + '%s' %bridgename, '%s' %bridgeportname, '0', '%s' %attrvalue]) + else: + self.subprocess_check_output(['/sbin/mstpctl', 'set%s' %attrname, + '%s' %bridgename, '%s' %bridgeportname, '%s' %attrvalue]) + + def get_bridge_attrs(self, bridgename): + bridgeattrs = {} + try: + bridgeattrs = dict((k, self.get_bridge_attr(bridgename, k)) + for k in self._bridgeattrmap.keys()) + bridgeattrs['treeprio'] = '%d' %(int(bridgeattrs.get('bridgeid', + '').split('.')[0], base=16) * 4096) + del bridgeattrs['bridgeid'] + except Exception, e: + self.logger.debug(bridgeattrs) + self.logger.debug(str(e)) + pass + return bridgeattrs + + def get_bridge_attr(self, bridgename, attrname): + try: + return self.subprocess_check_output(['/sbin/mstpctl', + 'showbridge', '%s' %bridgename, + self._bridgeattrmap[attrname]]).strip('\n') + except Exception, e: + pass + return None + + def set_bridge_attr(self, bridgename, attrname, attrvalue, check=True): + + if check: + attrvalue_curr = self.get_bridge_attr(bridgename, attrname) + if attrvalue_curr and attrvalue_curr == attrvalue: + return + if attrname == 'treeprio': + self.subprocess_check_call(['/sbin/mstpctl', 'set%s' %attrname, + '%s' %bridgename, '0', '%s' %attrvalue]) + else: + self.subprocess_check_call(['/sbin/mstpctl', 'set%s' %attrname, + '%s' %bridgename, '%s' %attrvalue]) + + def set_bridge_attrs(self, bridgename, attrdict, check=True): + for k, v in attrdict.iteritems(): + if not v: + continue + try: + self.set_bridge_attr(bridgename, k, v, check) + except Exception, e: + self.logger.warn('%s: %s' %(bridgename, str(e))) + pass + + def get_bridge_treeprio(self, bridgename): + try: + bridgeid = subprocess.check_output(['/sbin/mstpctl', + 'showbridge', '%s' %bridgename, + self._bridgeattrmap['bridgeid']]).strip('\n') + return '%d' %(int(bridgeid.split('.')[0], base=16) * 4096) + except: + pass + return None + + def set_bridge_treeprio(self, bridgename, attrvalue, check=True): + if check: + attrvalue_curr = self.get_bridge_treeprio(bridgename) + if attrvalue_curr and attrvalue_curr == attrvalue: + return + self.subprocess_check_output(['/sbin/mstpctl', 'settreeprio', + '%s' %bridgename, '0', '%s' %attrvalue]) + + def showbridge(self, bridgename=None): + if bridgename: + return self.exec_command('/sbin/mstpctl showbridge %s' %bridgename) + else: + return self.exec_command('/sbin/mstpctl showbridge') + + def showportdetail(self, bridgename): + return self.exec_command('/sbin/mstpctl showportdetail %s' %bridgename) + + def mstpbridge_exists(self, bridgename): + try: + subprocess.check_call('mstpctl showbridge %s' %bridgename) + return True + except: + return False diff --git a/ifupdown2/ifupdownaddons/utilsbase.py b/ifupdown2/ifupdownaddons/utilsbase.py new file mode 100644 index 0000000..73fdfd5 --- /dev/null +++ b/ifupdown2/ifupdownaddons/utilsbase.py @@ -0,0 +1,163 @@ +#!/usr/bin/python +# +# Copyright 2014 Cumulus Networks, Inc. All rights reserved. +# Author: Roopa Prabhu, roopa@cumulusnetworks.com +# + +import logging +import subprocess +import re +import io +from ifupdown.iface import * +from cache import * + +#import timeit +import time +import logging + +def profile(func): + def wrap(*args, **kwargs): + started_at = time.time() + result = func(*args, **kwargs) + print str(func) + print (time.time() - started_at) + return result + return wrap + +class utilsBase(object): + """ Base class for ifupdown addon utilities """ + + def __init__(self, *args, **kargs): + modulename = self.__class__.__name__ + self.logger = logging.getLogger('ifupdown.' + modulename) + self.FORCE = kargs.get('force', False) + self.DRYRUN = kargs.get('dryrun', False) + self.NOWAIT = kargs.get('nowait', False) + self.PERFMODE = kargs.get('perfmode', False) + self.CACHE = kargs.get('cache', False) + + def exec_commandl(self, cmdl, cmdenv=None): + """ Executes command """ + + cmd_returncode = 0 + cmdout = '' + try: + self.logger.info('executing ' + ' '.join(cmdl)) + if self.DRYRUN: + return cmdout + ch = subprocess.Popen(cmdl, + stdout=subprocess.PIPE, + shell=False, env=cmdenv, + stderr=subprocess.STDOUT, + close_fds=True) + cmdout = ch.communicate()[0] + cmd_returncode = ch.wait() + except OSError, e: + raise Exception('failed to execute cmd \'%s\' (%s)' + %(' '.join(cmdl), str(e))) + if cmd_returncode != 0: + raise Exception('failed to execute cmd \'%s\'' + %' '.join(cmdl) + '(' + cmdout.strip('\n ') + ')') + return cmdout + + def exec_command(self, cmd, cmdenv=None): + """ Executes command given as string in the argument cmd """ + + return self.exec_commandl(cmd.split(), cmdenv) + + def exec_command_talk_stdin(self, cmd, stdinbuf): + """ Executes command and writes to stdin of the process """ + cmd_returncode = 0 + cmdout = '' + try: + self.logger.info('executing %s [%s]' %(cmd, stdinbuf)) + if self.DRYRUN: + return cmdout + ch = subprocess.Popen(cmd.split(), + stdout=subprocess.PIPE, + stdin=subprocess.PIPE, + shell=False, + stderr=subprocess.STDOUT, + close_fds=True) + cmdout = ch.communicate(input=stdinbuf)[0] + cmd_returncode = ch.wait() + except OSError, e: + raise Exception('failed to execute cmd \'%s\' (%s)' + %(cmd, str(e))) + if cmd_returncode != 0: + raise Exception('failed to execute cmd \'%s [%s]\'' + %(cmd, stdinbuf) + '(' + cmdout.strip('\n ') + ')') + return cmdout + + def subprocess_check_output(self, cmdl): + self.logger.info('executing ' + ' '.join(cmdl)) + if self.DRYRUN: + return + try: + return subprocess.check_output(cmdl, stderr=subprocess.STDOUT) + except Exception, e: + raise Exception('failed to execute cmd \'%s\' (%s)' + %(' '.join(cmdl), e.output)) + + def subprocess_check_call(self, cmdl): + """ subprocess check_call implementation using popen + + Uses popen because it needs the close_fds argument + """ + + cmd_returncode = 0 + try: + self.logger.info('executing ' + ' '.join(cmdl)) + if self.DRYRUN: + return + ch = subprocess.Popen(cmdl, + stdout=None, + shell=False, + stderr=None, + close_fds=True) + cmd_returncode = ch.wait() + except Exception, e: + raise Exception('failed to execute cmd \'%s\' (%s)' + %(' '.join(cmdl), str(e))) + if cmd_returncode != 0: + raise Exception('failed to execute cmd \'%s\'' + %' '.join(cmdl)) + return + + def write_file(self, filename, strexpr): + try: + self.logger.info('writing \'%s\'' %strexpr + + ' to file %s' %filename) + if self.DRYRUN: + return 0 + with open(filename, 'w') as f: + f.write(strexpr) + except IOError, e: + self.logger.warn('error writing to file %s' + %filename + '(' + str(e) + ')') + return -1 + return 0 + + def read_file(self, filename): + try: + self.logger.debug('reading \'%s\'' %filename) + with open(filename, 'r') as f: + return f.readlines() + except: + return None + return None + + def read_file_oneline(self, filename): + try: + self.logger.debug('reading \'%s\'' %filename) + with open(filename, 'r') as f: + return f.readline().strip('\n') + except: + return None + return None + + def sysctl_set(self, variable, value): + self.exec_command('sysctl %s=' %variable + '%s' %value) + + def sysctl_get(self, variable): + return self.exec_command('sysctl %s' %variable).split('=')[1].strip() diff --git a/ifupdown2/init.d/networking b/ifupdown2/init.d/networking index df5e43f..6e5e070 100644 --- a/ifupdown2/init.d/networking +++ b/ifupdown2/init.d/networking @@ -22,15 +22,14 @@ SCRIPTNAME=/etc/init.d/$NAME . /lib/lsb/init-functions CONFIGURE_INTERFACES=yes -EXCLUDE_INTERFACES= -REMOTE_SYSLOG_SERVER= -VERBOSE=no -verbose= +EXTRA_ARGS= [ -f /etc/default/networking ] && . /etc/default/networking -[ "$VERBOSE" = yes ] && verbose=-v +[ "$VERBOSE" = yes ] && EXTRA_ARGS=-v +[ "$DEBUG" = yes ] && EXTRA_ARGS="$EXTRA_ARGS -d" +[ "$SYSLOG" = yes ] && EXTRA_ARGS="$EXTRA_ARGS --syslog" gen_examples() { # Generate sample interfaces file. The interfaces files are @@ -51,20 +50,11 @@ gen_examples() { return } -is_bootup() { - # Return 0 if its bootup or return 1 - [ -f /var/tmp/network/ifstatenew ] && return 1 - - return 0 -} - perf_options() { # At bootup lets set perfmode - if is_bootup ; then - echo -n "--perfmode" - else - echo -n "" - fi + [ -f /var/tmp/network/ifstatenew ] && echo -n "" && return + + echo -n "--perfmode" } process_exclusions() { @@ -152,12 +142,7 @@ start) exclusions=$(process_exclusions) perfoptions=$(perf_options) log_action_begin_msg "Configuring network interfaces" - if is_bootup ; then - ifup -a $verbose $perfoptions 2>&1 | /usr/bin/logger \ - $REMOTE_SYSLOG_SERVER -s -i -t $SCRIPTNAME - else - ifup -a $verbose $perfoptions - fi + ifup -a $EXTRA_ARGS $exclusions $perfoptions log_action_end_msg $? ;; @@ -165,13 +150,11 @@ stop) ifupdown_init check_network_file_systems check_network_swap + exclusions=$(process_exclusions) log_action_begin_msg "Deconfiguring network interfaces" - if ifdown -a --exclude=lo $verbose; then - log_action_end_msg $? - else - log_action_end_msg $? - fi + ifdown -a $EXTRA_ARGS $exclusions + log_action_end_msg $? ;; reload) @@ -179,12 +162,17 @@ reload) ifupdown_init log_action_begin_msg "Reloading network interfaces configuration" - if ifreload -a - then - log_action_end_msg $? - else - log_action_end_msg $? - fi + ifreload -a $EXTRA_ARGS + log_action_end_msg $? + ;; + +reload-currently-up) + + ifupdown_init + log_action_begin_msg "Reloading currently up network interfaces configuration" + + ifreload --currently-up $EXTRA_ARGS + log_action_end_msg $? ;; force-reload) @@ -192,27 +180,19 @@ force-reload) ifupdown_init log_action_begin_msg "Reloading network interfaces configuration" - if ifreload -a - then - log_action_end_msg $? - else - log_action_end_msg $? - fi + ifreload -f -a $EXTRA_ARGS + log_action_end_msg $? ;; restart) ifupdown_init - log_action_begin_msg "Reconfiguring network interfaces" - ifdown -a --exclude=lo $verbose || true set -f exclusions=$(process_exclusions) - if ifup -a --exclude=lo $verbose - then - log_action_end_msg $? - else - log_action_end_msg $? - fi + log_action_begin_msg "Reconfiguring network interfaces" + ifdown -a $EXTRA_ARGS $exclusions || true + ifup -a $EXTRA_ARGS $exclusions + log_action_end_msg $? ;; *) diff --git a/ifupdown2/man.rst/ifquery.8.rst b/ifupdown2/man.rst/ifquery.8.rst index 1ed7939..ba443ce 100644 --- a/ifupdown2/man.rst/ifquery.8.rst +++ b/ifupdown2/man.rst/ifquery.8.rst @@ -50,8 +50,7 @@ OPTIONS -v, --verbose verbose -d, --debug output debug info - - -l, --allow CLASS ignore non-"allow-CLASS" interfaces + --allow CLASS ignore non-"allow-CLASS" interfaces -w, --with-depends run with all dependent interfaces. This option is redundant when -a is specified. When '-a' is @@ -66,6 +65,9 @@ OPTIONS Use interfaces file instead of default /etc/network/interfaces + -t {native,json}, --interfaces-format {native,json} + interfaces file format + -r, --running print raw interfaces file entries -c, --check check interface file contents against running state @@ -74,7 +76,6 @@ OPTIONS -x, --raw print raw config file entries - -o {native,json}, --format {native,json} interface display format diff --git a/ifupdown2/man.rst/ifreload.8.rst b/ifupdown2/man.rst/ifreload.8.rst index 9f17546..397a662 100644 --- a/ifupdown2/man.rst/ifreload.8.rst +++ b/ifupdown2/man.rst/ifreload.8.rst @@ -14,7 +14,7 @@ reload network interface configuration SYNOPSIS ======== - ifreload [-h] [-a] [-v] [-d] [-f] [-n] + ifreload [-h] (-a|-c) [-v] [-d] [-f] [-n] DESCRIPTION =========== @@ -27,6 +27,10 @@ DESCRIPTION but it skips **ifdown** for interfaces that did not change in the config file. + If you do not wish to execute **down** on any interfaces, but only **up** on + interfaces that were already **up**, please see the **--currently-up** + option below. + OPTIONS ======= @@ -40,6 +44,11 @@ OPTIONS -f, --force force run all operations + -c, --currently-up only reload auto and other interfaces that are + currently up. This can be used as a non-disruptive + alternative to -a because it will not down any + interfaces + EXAMPLES ======== # reload all auto interfaces in **interfaces(5)** file @@ -50,6 +59,10 @@ EXAMPLES **service networking reload** + # reload all currently up interfaces without bringing any interfaces down + + **service networking reload-currently-up** + SEE ALSO ======== ifup(8), diff --git a/ifupdown2/man.rst/ifup.8.rst b/ifupdown2/man.rst/ifup.8.rst index 2176935..407b44b 100644 --- a/ifupdown2/man.rst/ifup.8.rst +++ b/ifupdown2/man.rst/ifup.8.rst @@ -72,8 +72,7 @@ OPTIONS -v, --verbose verbose -d, --debug output debug info - - -l, --allow CLASS ignore non-"allow-CLASS" interfaces + --allow CLASS ignore non-"allow-CLASS" interfaces -w, --with-depends run with all dependent interfaces. This option is redundant when -a is specified. When '-a' is @@ -84,6 +83,13 @@ OPTIONS Exclude interfaces from the list of interfaces to operate on. Can be specified multiple times + -i INTERFACESFILE, --interfaces INTERFACESFILE + Use interfaces file instead of default + /etc/network/interfaces + + -t {native,json}, --interfaces-format {native,json} + interfaces file format + -f, --force force run all operations -n, --no-act print out what would happen, but don't do it diff --git a/ifupdown2/man.rst/ifupdown-addons-interfaces.5.rst b/ifupdown2/man.rst/ifupdown-addons-interfaces.5.rst new file mode 100644 index 0000000..24d18c7 --- /dev/null +++ b/ifupdown2/man.rst/ifupdown-addons-interfaces.5.rst @@ -0,0 +1,1079 @@ +========================== +ifupdown-addons-interfaces +========================== +--------------------------------------------------------- +ifupdown2 addon modules interface configuration +--------------------------------------------------------- +:Author: roopa@cumulusnetworks.com +:Date: 2013-09-25 +:Copyright: Copyright 2013 Cumulus Networks, Inc. All rights reserved. +:Version: 0.1 +:Manual section: 5 + + +DESCRIPTION +=========== + ifupdown2 addon modules add incremental functionality to + core ifupdown2 tool. + + All installed addon modules are executed on every interface + listed in the interfaces file. Addon modules are installed under + /usr/share/ifupdownaddons. To see the list of active addon + modules, see ifaddon(8). + + Addon modules add new attributes to the interfaces(5) file. + Below is a list of attribute options provided by each module. + These can be listed under each iface section in the interfaces(5) + file. + + +EXAMPLES +======== + Listed below are addon modules and their supported attributes. + The attributes if applicable go under the iface section in the + interfaces(5) file. + + **ethtool**: ethtool configuration module for interfaces + + + **link-duplex** + + **help**: set link duplex + + + **required**: False + + **default**: half + + **validvals**: half,full + + **example**: + link-duplex full + + + **link-autoneg** + + **help**: set autonegotiation + + + **required**: False + + **default**: off + + **validvals**: on,off + + **example**: + link-autoneg on + + + **link-speed** + + **help**: set link speed + + + **required**: False + + **example**: + link-speed 1000 + + + + **bridge**: bridge configuration module + + + **bridge-mcqifaddr** + + **help**: set multicast query to use ifaddr + + + **required**: False + + **default**: 0 + + **example**: + bridge-mcqifaddr 0 + + + **bridge-gcint** + + **help**: bridge garbage collection interval in secs + + + **required**: False + + **default**: 4 + + **example**: + bridge-gcint 4 + + + **bridge-mcsqc** + + **help**: set multicast startup query count + + + **required**: False + + **default**: 2 + + **example**: + bridge-mcsqc 2 + + + **bridge-stp** + + **help**: bridge-stp yes/no + + + **required**: False + + **default**: no + + **validvals**: yes,on,off,no + + **example**: + bridge-stp no + + + **bridge-mcsqi** + + **help**: set multicast startup query interval (in secs) + + + **required**: False + + **default**: 31 + + **example**: + bridge-mcsqi 31 + + + **bridge-mcmi** + + **help**: set multicast membership interval (in secs) + + + **required**: False + + **default**: 260 + + **example**: + bridge-mcmi 260 + + + **bridge-ports** + + **help**: bridge ports + + + **required**: True + + **example**: + bridge-ports swp1.100 swp2.100 swp3.100 + + bridge-ports glob swp1-3.100 + + bridge-ports regex (swp[1|2|3].100) + + + **bridge-mcsnoop** + + **help**: set multicast snooping + + + **required**: False + + **default**: 1 + + **example**: + bridge-mcsnoop 1 + + + **bridge-maxwait** + + **help**: forces to time seconds the maximum time that the Deb + ian bridge setup scripts will wait for the bridge ports to ge + t to the forwarding status, doesn't allow factional part. If i + t is equal to 0 then no waiting is done + + + **required**: False + + **default**: 0 + + **example**: + bridge-maxwait 3 + + + **bridge-pathcosts** + + **help**: bridge set port path costs + + + **required**: False + + **default**: 100 + + **example**: + bridge-pathcosts swp1=100 swp2=100 + + + **bridge-portprios** + + **help**: bridge port prios + + + **required**: False + + **default**: 32 + + **example**: + bridge-portprios swp1=32 swp2=32 + + + **bridge-fd** + + **help**: bridge forward delay + + + **required**: False + + **default**: 15 + + **example**: + bridge-fd 15 + + + **bridge-ageing** + + **help**: bridge ageing + + + **required**: False + + **default**: 300 + + **example**: + bridge-ageing 300 + + + **bridge-hello** + + **help**: bridge set hello time + + + **required**: False + + **default**: 2 + + **example**: + bridge-hello 2 + + + **bridge-mcquerier** + + **help**: set multicast querier + + + **required**: False + + **default**: 0 + + **example**: + bridge-mcquerier 0 + + + **bridge-mclmc** + + **help**: set multicast last member count + + + **required**: False + + **default**: 2 + + **example**: + bridge-mclmc 2 + + + **bridge-mcrouter** + + **help**: set multicast router + + + **required**: False + + **default**: 1 + + **example**: + bridge-mcrouter 1 + + + **bridge-portmcrouter** + + **help**: set port multicast routers + + + **required**: False + + **default**: 1 + + **example**: + bridge-portmcrouter swp1=1 swp2=1 + + + **bridge-mclmi** + + **help**: set multicast last member interval (in secs) + + + **required**: False + + **default**: 1 + + **example**: + bridge-mclmi 1 + + + **bridge-hashmax** + + **help**: set hash max + + + **required**: False + + **default**: 4096 + + **example**: + bridge-hashmax 4096 + + + **bridge-waitport** + + **help**: wait for a max of time secs for the specified ports + to become available,if no ports are specified then those speci + fied on bridge-ports will be used here. Specifying no ports he + re should not be used if we are using regex or "all" on bridge + _ports,as it wouldnt work. + + + **required**: False + + **default**: 0 + + **example**: + bridge-waitport 4 + + + **bridge-mcqri** + + **help**: set multicast query response interval (in secs) + + + **required**: False + + **default**: 10 + + **example**: + bridge-mcqri 10 + + + **bridge-hashel** + + **help**: set hash elasticity + + + **required**: False + + **default**: 4096 + + **example**: + bridge-hashel 4096 + + + **bridge-mcqpi** + + **help**: set multicast querier interval (in secs) + + + **required**: False + + **default**: 255 + + **example**: + bridge-mcqpi 255 + + + **bridge-bridgeprio** + + **help**: bridge priority + + + **required**: False + + **default**: 32768 + + **example**: + bridge-bridgeprio 32768 + + + **bridge-maxage** + + **help**: bridge set maxage + + + **required**: False + + **default**: 20 + + **example**: + bridge-maxage 20 + + + **bridge-portmcfl** + + **help**: port multicast fast leave + + + **required**: False + + **default**: 0 + + **example**: + bridge-portmcfl swp1=0 swp2=0 + + + **bridge-mcqi** + + **help**: set multicast query interval (in secs) + + + **required**: False + + **default**: 125 + + **example**: + bridge-mcqi 125 + + + + **usercmds**: user commands for interfaces + + + **down** + + **help**: run command at interface down + + + **required**: False + + **post-up** + + **help**: run command after interface bring up + + + **required**: False + + **up** + + **help**: run command at interface bring up + + + **required**: False + + **pre-down** + + **help**: run command before bringing the interface down + + + **required**: False + + **pre-up** + + **help**: run command before bringing the interface up + + + **required**: False + + **post-down** + + **help**: run command after bringing interface down + + + **required**: False + + + **mstpctl**: mstp configuration module for bridges + + + **mstpctl-fdelay** + + **help**: set forwarding delay + + + **required**: False + + **default**: 15 + + **example**: + mstpctl-fdelay 15 + + + **mstpctl-txholdcount** + + **help**: bridge transmit holdcount + + + **required**: False + + **default**: 6 + + **example**: + mstpctl-txholdcount 6 + + + **mstpctl-portautoedge** + + **help**: enable/disable auto transition to/from edge state of + the port + + + **required**: False + + **default**: no + + **validvals**: yes,no + + **example**: + mstpctl-portautoedge swp1=yes swp2=yes + + + **mstpctl-portrestrrole** + + **help**: enable/disable port ability to take root role of the + port + + + **required**: False + + **default**: no + + **validvals**: yes,no + + **example**: + mstpctl-portrestrrole swp1=no swp2=no + + + **mstpctl-portnetwork** + + **help**: enable/disable bridge assurance capability for a por + t + + + **required**: False + + **default**: no + + **validvals**: yes,no + + **example**: + mstpctl-portnetwork swp1=no swp2=no + + + **mstpctl-portp2p** + + **help**: bridge port p2p detection mode + + + **required**: False + + **default**: no + + **validvals**: yes,no + + **example**: + mstpctl-portp2p swp1=no swp2=no + + + **mstpctl-treeprio** + + **help**: tree priority + + + **required**: False + + **default**: 32768 + + validrange: 0-65535 + + **example**: + mstpctl-treeprio 32768 + + + **mstpctl-treeportprio** + + **help**: port priority for MSTI instance + + + **required**: False + + **default**: 128 + + validrange: 0-240 + + **example**: + mstpctl-treeportprio swp1=128 swp2=128 + + + **mstpctl-hello** + + **help**: set hello time + + + **required**: False + + **default**: 2 + + **example**: + mstpctl-hello 2 + + + **mstpctl-ageing** + + **help**: ageing time + + + **required**: False + + **default**: 300 + + **example**: + mstpctl-ageing 300 + + + **mstpctl-portadminedge** + + **help**: enable/disable initial edge state of the port + + + **required**: False + + **default**: no + + **validvals**: yes,no + + **example**: + mstpctl-portadminedge swp1=no swp2=no + + + **mstpctl-maxage** + + **help**: max message age + + + **required**: False + + **default**: 20 + + **example**: + mstpctl-maxage 20 + + + **mstpctl-maxhops** + + **help**: bridge max hops + + + **required**: False + + **default**: 15 + + **example**: + mstpctl-maxhops 15 + + + **mstpctl-portrestrtcn** + + **help**: enable/disable port ability to propagate received to + pology change notification of the port + + + **required**: False + + **default**: no + + **validvals**: yes,no + + **example**: + mstpctl-portrestrtcn swp1=no swp2=no + + + **mstpctl-portpathcost** + + **help**: bridge port path cost + + + **required**: False + + **default**: 0 + + **example**: + mstpctl-portpathcost swp1=0 swp2=1 + + + **mstpctl-portadminage** + + **help**: bridge port admin age + + + **required**: False + + **default**: no + + **validvals**: yes,no + + **example**: + mstpctl-portadminage swp1=no swp2=no + + + **mstpctl-portbpdufilter** + + **help**: enable/disable bpdu filter on a port + + + **required**: False + + **default**: no + + **validvals**: yes,no + + **example**: + mstpctl-portbpdufilter swp1=no swp2=no + + + **mstpctl-forcevers** + + **help**: bridge force stp version + + + **required**: False + + **default**: rstp + + **example**: + mstpctl-forcevers rstp + + + **mstpctl-treeportcost** + + **help**: port tree cost + + + **required**: False + + **mstpctl-bpduguard** + + **help**: enable/disable bpduguard + + + **required**: False + + **default**: no + + **validvals**: yes,no + + **example**: + mstpctl-bpduguard swp1=no swp2=no + + + + **vlan**: vlan module configures vlan interfaces.This module under + stands vlan interfaces with dot notations. eg swp1.100. Vlan inter + faces with any other names need to have raw device and vlan id att + ributes + + + **vlan-id** + + **help**: vlan id + + + **required**: False + + **vlan-raw-device** + + **help**: vlan raw device + + + **required**: False + + + **ifenslave**: bond configuration module + + + **bond-miimon** + + **help**: bond miimon + + + **required**: False + + **default**: 0 + + validrange: 0-255 + + **example**: + bond-miimon 0 + + + **bond-slaves** + + **help**: bond slaves + + + **required**: True + + **example**: + bond-slaves swp1 swp2 + + bond-slaves glob swp1-2 + + bond-slaves regex (swp[1|2) + + + **bond-mode** + + **help**: bond mode + + + **required**: False + + **default**: balance-rr + + **validvals**: balance-rr,active-backup,balance-xor,broadcast,802.3ad,balance-tlb,balance-alb + + **example**: + bond-mode 802.3ad + + + **bond-num-grat-arp** + + **help**: bond use carrier + + + **required**: False + + **default**: 1 + + validrange: 0-255 + + **example**: + bond-num-grat-arp 1 + + + **bond-ad-sys-mac-addr** + + **help**: 802.3ad system mac address + + + **required**: False + + **default**: 00:00:00:00:00:00 + + **example**: + bond-ad-sys-mac-addr 00:00:00:00:00:00 + + + **bond-use-carrier** + + **help**: bond use carrier + + + **required**: False + + **default**: 1 + + **validvals**: 0,1 + + **example**: + bond-use-carrier 1 + + + **bond-lacp-rate** + + **help**: bond use carrier + + + **required**: False + + **default**: 0 + + **validvals**: 0,1 + + **example**: + bond-lacp-rate 0 + + + **bond-min-links** + + **help**: bond min links + + + **required**: False + + **default**: 0 + + **example**: + bond-min-links 0 + + + **bond-num-unsol-na** + + **help**: bond slave devices + + + **required**: False + + **default**: 1 + + validrange: 0-255 + + **example**: + bond-num-unsol-na 1 + + + **bond-ad-sys-priority** + + **help**: 802.3ad system priority + + + **required**: False + + **default**: 65535 + + **example**: + bond-ad-sys-priority 65535 + + + **bond-xmit-hash-policy** + + **help**: bond slave devices + + + **required**: False + + **default**: layer2 + + **validvals**: layer2,layer3+4,layer2+3 + + **example**: + bond-xmit-hash-policy layer2 + + + + **address**: address configuration module for interfaces + + + **broadcast** + + **help**: broadcast address + + + **required**: False + + **example**: + broadcast 10.0.1.255 + + + **hwaddress** + + **help**: hw address + + + **required**: False + + **example**: + hwaddress 44:38:39:00:27:b8 + + + **alias** + + **help**: description/alias + + + **required**: False + + **example**: + alias testnetwork + + + **address** + + **help**: ipv4 or ipv6 addresses + + + **required**: False + + **example**: + address 10.0.12.3/24 + + address 2000:1000:1000:1000:3::5/128 + + + **scope** + + **help**: scope + + + **required**: False + + **example**: + scope host + + + **preferred-lifetime** + + **help**: preferred lifetime + + + **required**: False + + **example**: + preferred-lifetime forever + + preferred-lifetime 10 + + + **gateway** + + **help**: default gateway + + + **required**: False + + **example**: + gateway 255.255.255.0 + + + **mtu** + + **help**: interface mtu + + + **required**: False + + **default**: 1500 + + **example**: + mtu 1600 + + + +SEE ALSO +======== + interfaces(5), + ifup(8), + ip(8), + mstpctl(8), + brctl(8), + ethtool(8) diff --git a/ifupdown2/man/ifquery.8 b/ifupdown2/man/ifquery.8 new file mode 100644 index 0000000..65e89e2 --- /dev/null +++ b/ifupdown2/man/ifquery.8 @@ -0,0 +1,230 @@ +.\" Man page generated from reStructeredText. +. +.TH IFQUERY 8 "2014-02-05" "0.1" "" +.SH NAME +ifquery \- query network interface configuration +. +.nr rst2man-indent-level 0 +. +.de1 rstReportMargin +\\$1 \\n[an-margin] +level \\n[rst2man-indent-level] +level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] +- +\\n[rst2man-indent0] +\\n[rst2man-indent1] +\\n[rst2man-indent2] +.. +.de1 INDENT +.\" .rstReportMargin pre: +. RS \\$1 +. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] +. nr rst2man-indent-level +1 +.\" .rstReportMargin post: +.. +.de UNINDENT +. RE +.\" indent \\n[an-margin] +.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] +.nr rst2man-indent-level -1 +.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] +.in \\n[rst2man-indent\\n[rst2man-indent-level]]u +.. +.SH SYNOPSIS +.INDENT 0.0 +.INDENT 3.5 +\fBifquery [\-v] [\-\-allow CLASS] [\-\-with\-depends] \-a|IFACE...\fP +.sp +\fBifquery [\-v] [\-r|\-\-running] [\-\-allow CLASS] [\-\-with\-depends] \-a|IFACE...\fP +.sp +\fBifquery [\-v] [\-c|\-\-check] [\-\-allow CLASS] [\-\-with\-depends] \-a|IFACE...\fP +.sp +\fBifquery [\-v] [\-p|\-\-print\-dependency {list,dot}] [\-\-allow CLASS] [\-\-with\-depends] \-a|IFACE...\fP +.sp +\fBifquery [\-v] \-s|\-\-syntax\-help\fP +.UNINDENT +.UNINDENT +.SH DESCRIPTION +.INDENT 0.0 +.INDENT 3.5 +\fBifquery\fP can be used to parse interface configuration file, query +running state or check running state of the interface with configuration +in \fB/etc/network/interfaces\fP file. +.sp +\fBifquery\fP always works on the current \fBinterfaces(5)\fP file +\fB/etc/network/interfaces\fP unless an alternate interfaces file is +provided with the \fB\-i\fP option. +.UNINDENT +.UNINDENT +.SH OPTIONS +.INDENT 0.0 +.INDENT 3.5 +positional arguments: +.sp +\fBIFACE\fP interface list separated by spaces. \fBIFACE\fP list and \fB\(aq\-a\(aq\fP argument are mutually exclusive. +.sp +optional arguments: +.INDENT 0.0 +.TP +.B \-h, \-\-help +show this help message and exit +.TP +.B \-a, \-\-all +process all interfaces marked "auto" +.TP +.B \-v, \-\-verbose +verbose +.TP +.B \-d, \-\-debug +output debug info +.TP +.BI \-\-allow \ CLASS +ignore non\-"allow\-CLASS" interfaces +.TP +.B \-w, \-\-with\-depends +run with all dependent interfaces. This option +is redundant when \-a is specified. When \(aq\-a\(aq is +specified, interfaces are always executed in +dependency order. +.TP +.BI \-X \ EXCLUDEPATS, \ \-\-exclude \ EXCLUDEPATS +Exclude interfaces from the list of interfaces to +operate on. Can be specified multiple times +.TP +.BI \-i \ INTERFACESFILE, \ \-\-interfaces \ INTERFACESFILE +Use interfaces file instead of default +/etc/network/interfaces +.UNINDENT +.INDENT 0.0 +.TP +.B \-t {native,json}, \-\-interfaces\-format {native,json} +interfaces file format +.UNINDENT +.INDENT 0.0 +.TP +.B \-r, \-\-running +print raw interfaces file entries +.TP +.B \-c, \-\-check +check interface file contents against running state +of an interface. Returns exit code 0 on success and +1 on error +.TP +.B \-x, \-\-raw +print raw config file entries +.UNINDENT +.INDENT 0.0 +.TP +.B \-o {native,json}, \-\-format {native,json} +interface display format +.TP +.B \-p, \-\-print\-dependency {list,dot} +print iface dependency in list or dot format +.UNINDENT +.INDENT 0.0 +.TP +.B \-s, \-\-syntax\-help +print supported interface config syntax. Scans all +addon modules and dumps supported syntax from them +if provided by the module. +.UNINDENT +.UNINDENT +.UNINDENT +.SH EXAMPLES +.INDENT 0.0 +.INDENT 3.5 +# dump all or some interfaces config file entries +# (pretty prints user provided entries) +.INDENT 0.0 +.INDENT 3.5 +\fBifquery \-a\fP +.sp +\fBifquery br0\fP +.UNINDENT +.UNINDENT +.sp +# Same as above but dump with dependencies +.INDENT 0.0 +.INDENT 3.5 +\fBifquery br0 \-\-with\-depends\fP +.UNINDENT +.UNINDENT +.sp +# Check running state with the config in /etc/network/interfaces +.INDENT 0.0 +.INDENT 3.5 +\fBifquery \-\-check br0\fP +.sp +\fBifquery \-\-check \-\-with\-depends br0\fP +.sp +\fBifquery \-\-check \-a\fP +.UNINDENT +.UNINDENT +.sp +# dump running state of all interfaces in /etc/network/interfaces format +.INDENT 0.0 +.INDENT 3.5 +\fBifquery \-\-running br0\fP +.sp +\fBifquery \-\-running \-\-with\-depends br0\fP +.sp +\fBifquery \-\-running \-a\fP +.UNINDENT +.UNINDENT +.sp +# print dependency info in list format +.INDENT 0.0 +.INDENT 3.5 +\fBifquery \-\-print\-dependency=list \-a\fP +.sp +\fBifquery \-\-print\-dependency=list br2000\fP +.UNINDENT +.UNINDENT +.sp +# print dependency info in dot format +.INDENT 0.0 +.INDENT 3.5 +\fBifquery \-\-print\-dependency=dot \-a\fP +.sp +\fBifquery \-\-print\-dependency=dot br2000\fP +.UNINDENT +.UNINDENT +.sp +# Create an image (png) from the dot format +.INDENT 0.0 +.INDENT 3.5 +\fBifquery \-\-print\-dependency=dot \-a > interfaces.dot\fP +.sp +\fBdot \-Tpng interfaces.dot > interfaces.png\fP +.sp +(The above command only works on a system with dot installed) +.UNINDENT +.UNINDENT +.UNINDENT +.UNINDENT +.SH KNOWN_ISSUES +.INDENT 0.0 +.INDENT 3.5 +\fBifquery \-\-check\fP is currently experimental +.sp +\fBifquery \-\-check\fP cannot validate usercommands given under pre\-up, post\-up etc +There is currently no support to check/validate ethtool iface attributes +.UNINDENT +.UNINDENT +.SH SEE ALSO +.INDENT 0.0 +.INDENT 3.5 +ifup(8), +ifdown(8), +ifreload(8), +interfaces(5), +ifupdown\-addons\-interfaces(5) +.UNINDENT +.UNINDENT +.SH AUTHOR +Roopa Prabhu +.SH COPYRIGHT +Copyright 2014 Cumulus Networks, Inc. All rights reserved. +.\" Generated by docutils manpage writer. +.\" +. diff --git a/ifupdown2/man/ifreload.8 b/ifupdown2/man/ifreload.8 new file mode 100644 index 0000000..a39da36 --- /dev/null +++ b/ifupdown2/man/ifreload.8 @@ -0,0 +1,116 @@ +.\" Man page generated from reStructeredText. +. +.TH IFRELOAD 8 "2014-02-05" "0.1" "" +.SH NAME +ifreload \- reload network interface configuration +. +.nr rst2man-indent-level 0 +. +.de1 rstReportMargin +\\$1 \\n[an-margin] +level \\n[rst2man-indent-level] +level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] +- +\\n[rst2man-indent0] +\\n[rst2man-indent1] +\\n[rst2man-indent2] +.. +.de1 INDENT +.\" .rstReportMargin pre: +. RS \\$1 +. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] +. nr rst2man-indent-level +1 +.\" .rstReportMargin post: +.. +.de UNINDENT +. RE +.\" indent \\n[an-margin] +.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] +.nr rst2man-indent-level -1 +.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] +.in \\n[rst2man-indent\\n[rst2man-indent-level]]u +.. +.SH SYNOPSIS +.INDENT 0.0 +.INDENT 3.5 +ifreload [\-h] (\-a|\-c) [\-v] [\-d] [\-f] [\-n] +.UNINDENT +.UNINDENT +.SH DESCRIPTION +.INDENT 0.0 +.INDENT 3.5 +reloads network \fBinterfaces(5)\fP file \fB/etc/network/interfaces\fP. +.sp +Runs \fBifdown\fP on interfaces that changed in the interfaces file and +subsequently runs \fBifup\fP on all interfaces. +.sp +\fBifreload\fP is equivalent to \fBifdown \-a\fP followed by \fBifup \-a\fP +but it skips \fBifdown\fP for interfaces that did not change in the config +file. +.sp +If you do not wish to execute \fBdown\fP on any interfaces, but only \fBup\fP on +interfaces that were already \fBup\fP, please see the \fB\-\-currently\-up\fP +option below. +.UNINDENT +.UNINDENT +.SH OPTIONS +.INDENT 0.0 +.INDENT 3.5 +.INDENT 0.0 +.TP +.B \-h, \-\-help +show this help message and exit +.TP +.B \-a, \-\-all +process all interfaces marked "auto" +.TP +.B \-v, \-\-verbose +verbose +.TP +.B \-d, \-\-debug +output debug info +.TP +.B \-f, \-\-force +force run all operations +.TP +.B \-c, \-\-currently\-up +only reload auto and other interfaces that are +currently up. This can be used as a non\-disruptive +alternative to \-a because it will not down any +interfaces +.UNINDENT +.UNINDENT +.UNINDENT +.SH EXAMPLES +.INDENT 0.0 +.INDENT 3.5 +# reload all auto interfaces in \fBinterfaces(5)\fP file +.sp +\fBifreload \-a\fP +.sp +# reload all interfaces using service command +.sp +\fBservice networking reload\fP +.sp +# reload all currently up interfaces without bringing any interfaces down +.sp +\fBservice networking reload\-currently\-up\fP +.UNINDENT +.UNINDENT +.SH SEE ALSO +.INDENT 0.0 +.INDENT 3.5 +ifup(8), +ifdown(8), +ifquery(8), +interfaces(5), +ifupdown\-addons\-interfaces(5) +.UNINDENT +.UNINDENT +.SH AUTHOR +Roopa Prabhu +.SH COPYRIGHT +Copyright 2014 Cumulus Networks, Inc. All rights reserved. +.\" Generated by docutils manpage writer. +.\" +. diff --git a/ifupdown2/man/ifup.8 b/ifupdown2/man/ifup.8 new file mode 100644 index 0000000..ae72d9f --- /dev/null +++ b/ifupdown2/man/ifup.8 @@ -0,0 +1,269 @@ +.\" Man page generated from reStructeredText. +. +.TH IFUP 8 "2014-02-05" "0.1" "" +.SH NAME +ifup \- network interface management commands +. +.nr rst2man-indent-level 0 +. +.de1 rstReportMargin +\\$1 \\n[an-margin] +level \\n[rst2man-indent-level] +level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] +- +\\n[rst2man-indent0] +\\n[rst2man-indent1] +\\n[rst2man-indent2] +.. +.de1 INDENT +.\" .rstReportMargin pre: +. RS \\$1 +. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] +. nr rst2man-indent-level +1 +.\" .rstReportMargin post: +.. +.de UNINDENT +. RE +.\" indent \\n[an-margin] +.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] +.nr rst2man-indent-level -1 +.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] +.in \\n[rst2man-indent\\n[rst2man-indent-level]]u +.. +.SH NAME +.INDENT 0.0 +.INDENT 3.5 +\fBifup\fP \- bring a network interface up +.sp +\fBifdown\fP \- take a network interface down +.UNINDENT +.UNINDENT +.SH SYNOPSIS +.INDENT 0.0 +.INDENT 3.5 +.INDENT 0.0 +.TP +.B ifup [\-h] [\-a] [\-v] [\-d] [\-\-allow CLASS] [\-\-with\-depends] +\fB[\-X EXCLUDEPATS] [\-f] [\-n] [\-\-print\-dependency {list,dot}]\fP +\fB[IFACE [IFACE ...]]\fP +.TP +.B ifdown [\-h] [\-a] [\-v] [\-d] [\-\-allow CLASS] [\-\-with\-depends] +\fB[\-X EXCLUDEPATS] [\-f] [\-n] [\-\-print\-dependency {list,dot}]\fP +\fB[IFACE [IFACE ...]]\fP +.UNINDENT +.UNINDENT +.UNINDENT +.SH DESCRIPTION +.INDENT 0.0 +.INDENT 3.5 +\fBifup\fP and \fBifdown\fP commands can be used to configure (or, respectively, +deconfigure) network interfaces based on interface definitions in the +file \fB/etc/network/interfaces/\fP file. +.sp +\fBifquery(8)\fP maybe used in conjunction with \fBifup\fP and \fBifdown\fP +commands to query and validate applied/running configuration. +.sp +\fBifup\fP always works on the current \fBinterfaces(5)\fP file under +\fB/etc/network/interfaces\fP. \fBifdown\fP works on the last applied interface +configuration. +.sp +\fBifup\fP on an already ifup\(aqed interface will re\-apply the configuration, +skipping already applied configuration whereever possible. In many cases +where config commands are idempotent, you will see that ifup/ifdown will +reapply the config even if the interface already has that config. +.sp +\fBifup\fP and \fBifdown\fP understands interface dependency order. +.sp +For logical interfaces like vlans, bridges, bonds, \fBifup\fP creates the +interface and \fBifdown\fP deletes the interface. Use \fB\-\-admin\-state\fP +option if you only want to administratively bring the interface up/down. +.sp +When \fBifup\fP and \fBifdown\fP are used with interfaces on command line, +they must be have a \fBiface\fP section in the \fBinterfaces(5)\fP file. +.UNINDENT +.UNINDENT +.SH OPTIONS +.INDENT 0.0 +.INDENT 3.5 +positional arguments: +.sp +\fBIFACE\fP interface list separated by spaces. \fBIFACE\fP list and \fB\(aq\-a\(aq\fP +argument are mutually exclusive. +.sp +optional arguments: +.INDENT 0.0 +.TP +.B \-h, \-\-help +show this help message and exit +.TP +.B \-a, \-\-all +process all interfaces marked "auto" +.TP +.B \-v, \-\-verbose +verbose +.TP +.B \-d, \-\-debug +output debug info +.TP +.BI \-\-allow \ CLASS +ignore non\-"allow\-CLASS" interfaces +.TP +.B \-w, \-\-with\-depends +run with all dependent interfaces. This option +is redundant when \-a is specified. When \(aq\-a\(aq is +specified, interfaces are always executed in +dependency order. +.TP +.BI \-X \ EXCLUDEPATS, \ \-\-exclude \ EXCLUDEPATS +Exclude interfaces from the list of interfaces to +operate on. Can be specified multiple times +.TP +.BI \-i \ INTERFACESFILE, \ \-\-interfaces \ INTERFACESFILE +Use interfaces file instead of default +/etc/network/interfaces +.UNINDENT +.INDENT 0.0 +.TP +.B \-t {native,json}, \-\-interfaces\-format {native,json} +interfaces file format +.UNINDENT +.INDENT 0.0 +.TP +.B \-f, \-\-force +force run all operations +.TP +.B \-n, \-\-no\-act +print out what would happen, but don\(aqt do it +.UNINDENT +.INDENT 0.0 +.TP +.B \-p, \-\-print\-dependency {list,dot} +print iface dependency in list or dot format +.UNINDENT +.INDENT 0.0 +.TP +.B \-m, \-\-admin\-state, \-\-no\-scripts +dont run any addon modules/scripts. Only bring +the interface administratively up/down +.TP +.B \-u, \-\-use\-current\-config +By default ifdown looks at the saved state for +interfaces to bring down. This option allows ifdown +to look at the current interfaces file. Useful when +your state file is corrupted or you want down to use +the latest from the interfaces file +.UNINDENT +.UNINDENT +.UNINDENT +.SH EXAMPLES +.INDENT 0.0 +.INDENT 3.5 +# bringing up all interfaces +.INDENT 0.0 +.INDENT 3.5 +\fBifup \-a\fP +.UNINDENT +.UNINDENT +.sp +# bringing up interface list +.INDENT 0.0 +.INDENT 3.5 +\fBifup swp1 swp2\fP +.UNINDENT +.UNINDENT +.sp +# bringing up interface with its dependents +.INDENT 0.0 +.INDENT 3.5 +\fBifup br0 \-\-with\-depends\fP +.UNINDENT +.UNINDENT +.sp +# bringing down all interfaces +.INDENT 0.0 +.INDENT 3.5 +\fBifdown \-a\fP +.UNINDENT +.UNINDENT +.sp +# bringing down a single interface +.INDENT 0.0 +.INDENT 3.5 +\fBifdown swp1\fP +.UNINDENT +.UNINDENT +.sp +# excluding interfaces using \-X option +.INDENT 0.0 +.INDENT 3.5 +\fBifdown \-X eth0 \-a\fP +.sp +\fBifup \-X eth0 \-a\fP +.sp +\fBifdown \-X eth0 \-X lo \-a\fP +.UNINDENT +.UNINDENT +.sp +# using verbose \-v option to see what is going on +.INDENT 0.0 +.INDENT 3.5 +\fBifup \-v \-a\fP +.UNINDENT +.UNINDENT +.sp +# using debug \-d option to see more of what is going on +.INDENT 0.0 +.INDENT 3.5 +\fBifup \-d \-a\fP +.UNINDENT +.UNINDENT +.sp +# ignore errors +.INDENT 0.0 +.INDENT 3.5 +\fBifup \-a \-f\fP +.sp +\fBifdown \-a \-f\fP +.UNINDENT +.UNINDENT +.sp +# run ifdown and ifup on all interfaces using service command/init script +.INDENT 0.0 +.INDENT 3.5 +\fBservice networking restart\fP +.UNINDENT +.UNINDENT +.sp +# run ifup on all interfaces using service command/init script +.INDENT 0.0 +.INDENT 3.5 +\fBservice networking start\fP +.UNINDENT +.UNINDENT +.sp +# ifdown on all interfaces using service command/init script +.INDENT 0.0 +.INDENT 3.5 +\fBservice networking stop\fP +.UNINDENT +.UNINDENT +.sp +# To run ifup/ifdown on only interfaces that changed see \fBifreload(8)\fP +.UNINDENT +.UNINDENT +.SH SEE ALSO +.INDENT 0.0 +.INDENT 3.5 +ifquery(8), +ifreload(8), +interfaces(5), +ifupdown\-addons\-interfaces(5) +.UNINDENT +.UNINDENT +.SH AUTHOR +Roopa Prabhu +.SH COPYRIGHT +Copyright 2014 Cumulus Networks, Inc. All rights reserved. +.\" Generated by docutils manpage writer. +.\" +. diff --git a/ifupdown2/man/ifupdown-addons-interfaces.5 b/ifupdown2/man/ifupdown-addons-interfaces.5 new file mode 100644 index 0000000..86d71a7 --- /dev/null +++ b/ifupdown2/man/ifupdown-addons-interfaces.5 @@ -0,0 +1,1343 @@ +.\" Man page generated from reStructeredText. +. +.TH IFUPDOWN-ADDONS-INTERFACES 5 "2013-09-25" "0.1" "" +.SH NAME +ifupdown-addons-interfaces \- ifupdown2 addon modules interface configuration +. +.nr rst2man-indent-level 0 +. +.de1 rstReportMargin +\\$1 \\n[an-margin] +level \\n[rst2man-indent-level] +level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] +- +\\n[rst2man-indent0] +\\n[rst2man-indent1] +\\n[rst2man-indent2] +.. +.de1 INDENT +.\" .rstReportMargin pre: +. RS \\$1 +. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] +. nr rst2man-indent-level +1 +.\" .rstReportMargin post: +.. +.de UNINDENT +. RE +.\" indent \\n[an-margin] +.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] +.nr rst2man-indent-level -1 +.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] +.in \\n[rst2man-indent\\n[rst2man-indent-level]]u +.. +.SH DESCRIPTION +.INDENT 0.0 +.INDENT 3.5 +ifupdown2 addon modules add incremental functionality to +core ifupdown2 tool. +.sp +All installed addon modules are executed on every interface +listed in the interfaces file. Addon modules are installed under +/usr/share/ifupdownaddons. To see the list of active addon +modules, see ifaddon(8). +.sp +Addon modules add new attributes to the interfaces(5) file. +Below is a list of attribute options provided by each module. +These can be listed under each iface section in the interfaces(5) +file. +.UNINDENT +.UNINDENT +.SH EXAMPLES +.INDENT 0.0 +.INDENT 3.5 +Listed below are addon modules and their supported attributes. +The attributes if applicable go under the iface section in the +interfaces(5) file. +.sp +\fBethtool\fP: ethtool configuration module for interfaces +.INDENT 0.0 +.INDENT 3.5 +\fBlink\-duplex\fP +.INDENT 0.0 +.INDENT 3.5 +\fBhelp\fP: set link duplex +.sp +\fBrequired\fP: False +.sp +\fBdefault\fP: half +.sp +\fBvalidvals\fP: half,full +.INDENT 0.0 +.TP +.B \fBexample\fP: +link\-duplex full +.UNINDENT +.UNINDENT +.UNINDENT +.sp +\fBlink\-autoneg\fP +.INDENT 0.0 +.INDENT 3.5 +\fBhelp\fP: set autonegotiation +.sp +\fBrequired\fP: False +.sp +\fBdefault\fP: off +.sp +\fBvalidvals\fP: on,off +.INDENT 0.0 +.TP +.B \fBexample\fP: +link\-autoneg on +.UNINDENT +.UNINDENT +.UNINDENT +.sp +\fBlink\-speed\fP +.INDENT 0.0 +.INDENT 3.5 +\fBhelp\fP: set link speed +.sp +\fBrequired\fP: False +.INDENT 0.0 +.TP +.B \fBexample\fP: +link\-speed 1000 +.UNINDENT +.UNINDENT +.UNINDENT +.UNINDENT +.UNINDENT +.sp +\fBbridge\fP: bridge configuration module +.INDENT 0.0 +.INDENT 3.5 +\fBbridge\-mcqifaddr\fP +.INDENT 0.0 +.INDENT 3.5 +\fBhelp\fP: set multicast query to use ifaddr +.sp +\fBrequired\fP: False +.sp +\fBdefault\fP: 0 +.INDENT 0.0 +.TP +.B \fBexample\fP: +bridge\-mcqifaddr 0 +.UNINDENT +.UNINDENT +.UNINDENT +.sp +\fBbridge\-gcint\fP +.INDENT 0.0 +.INDENT 3.5 +\fBhelp\fP: bridge garbage collection interval in secs +.sp +\fBrequired\fP: False +.sp +\fBdefault\fP: 4 +.INDENT 0.0 +.TP +.B \fBexample\fP: +bridge\-gcint 4 +.UNINDENT +.UNINDENT +.UNINDENT +.sp +\fBbridge\-mcsqc\fP +.INDENT 0.0 +.INDENT 3.5 +\fBhelp\fP: set multicast startup query count +.sp +\fBrequired\fP: False +.sp +\fBdefault\fP: 2 +.INDENT 0.0 +.TP +.B \fBexample\fP: +bridge\-mcsqc 2 +.UNINDENT +.UNINDENT +.UNINDENT +.sp +\fBbridge\-stp\fP +.INDENT 0.0 +.INDENT 3.5 +\fBhelp\fP: bridge\-stp yes/no +.sp +\fBrequired\fP: False +.sp +\fBdefault\fP: no +.sp +\fBvalidvals\fP: yes,on,off,no +.INDENT 0.0 +.TP +.B \fBexample\fP: +bridge\-stp no +.UNINDENT +.UNINDENT +.UNINDENT +.sp +\fBbridge\-mcsqi\fP +.INDENT 0.0 +.INDENT 3.5 +\fBhelp\fP: set multicast startup query interval (in secs) +.sp +\fBrequired\fP: False +.sp +\fBdefault\fP: 31 +.INDENT 0.0 +.TP +.B \fBexample\fP: +bridge\-mcsqi 31 +.UNINDENT +.UNINDENT +.UNINDENT +.sp +\fBbridge\-mcmi\fP +.INDENT 0.0 +.INDENT 3.5 +\fBhelp\fP: set multicast membership interval (in secs) +.sp +\fBrequired\fP: False +.sp +\fBdefault\fP: 260 +.INDENT 0.0 +.TP +.B \fBexample\fP: +bridge\-mcmi 260 +.UNINDENT +.UNINDENT +.UNINDENT +.sp +\fBbridge\-ports\fP +.INDENT 0.0 +.INDENT 3.5 +\fBhelp\fP: bridge ports +.sp +\fBrequired\fP: True +.INDENT 0.0 +.TP +.B \fBexample\fP: +bridge\-ports swp1.100 swp2.100 swp3.100 +.sp +bridge\-ports glob swp1\-3.100 +.sp +bridge\-ports regex (swp[1|2|3].100) +.UNINDENT +.UNINDENT +.UNINDENT +.sp +\fBbridge\-mcsnoop\fP +.INDENT 0.0 +.INDENT 3.5 +\fBhelp\fP: set multicast snooping +.sp +\fBrequired\fP: False +.sp +\fBdefault\fP: 1 +.INDENT 0.0 +.TP +.B \fBexample\fP: +bridge\-mcsnoop 1 +.UNINDENT +.UNINDENT +.UNINDENT +.sp +\fBbridge\-maxwait\fP +.INDENT 0.0 +.INDENT 3.5 +\fBhelp\fP: forces to time seconds the maximum time that the Deb +ian bridge setup scripts will wait for the bridge ports to ge +t to the forwarding status, doesn\(aqt allow factional part. If i +t is equal to 0 then no waiting is done +.sp +\fBrequired\fP: False +.sp +\fBdefault\fP: 0 +.INDENT 0.0 +.TP +.B \fBexample\fP: +bridge\-maxwait 3 +.UNINDENT +.UNINDENT +.UNINDENT +.sp +\fBbridge\-pathcosts\fP +.INDENT 0.0 +.INDENT 3.5 +\fBhelp\fP: bridge set port path costs +.sp +\fBrequired\fP: False +.sp +\fBdefault\fP: 100 +.INDENT 0.0 +.TP +.B \fBexample\fP: +bridge\-pathcosts swp1=100 swp2=100 +.UNINDENT +.UNINDENT +.UNINDENT +.sp +\fBbridge\-portprios\fP +.INDENT 0.0 +.INDENT 3.5 +\fBhelp\fP: bridge port prios +.sp +\fBrequired\fP: False +.sp +\fBdefault\fP: 32 +.INDENT 0.0 +.TP +.B \fBexample\fP: +bridge\-portprios swp1=32 swp2=32 +.UNINDENT +.UNINDENT +.UNINDENT +.sp +\fBbridge\-fd\fP +.INDENT 0.0 +.INDENT 3.5 +\fBhelp\fP: bridge forward delay +.sp +\fBrequired\fP: False +.sp +\fBdefault\fP: 15 +.INDENT 0.0 +.TP +.B \fBexample\fP: +bridge\-fd 15 +.UNINDENT +.UNINDENT +.UNINDENT +.sp +\fBbridge\-ageing\fP +.INDENT 0.0 +.INDENT 3.5 +\fBhelp\fP: bridge ageing +.sp +\fBrequired\fP: False +.sp +\fBdefault\fP: 300 +.INDENT 0.0 +.TP +.B \fBexample\fP: +bridge\-ageing 300 +.UNINDENT +.UNINDENT +.UNINDENT +.sp +\fBbridge\-hello\fP +.INDENT 0.0 +.INDENT 3.5 +\fBhelp\fP: bridge set hello time +.sp +\fBrequired\fP: False +.sp +\fBdefault\fP: 2 +.INDENT 0.0 +.TP +.B \fBexample\fP: +bridge\-hello 2 +.UNINDENT +.UNINDENT +.UNINDENT +.sp +\fBbridge\-mcquerier\fP +.INDENT 0.0 +.INDENT 3.5 +\fBhelp\fP: set multicast querier +.sp +\fBrequired\fP: False +.sp +\fBdefault\fP: 0 +.INDENT 0.0 +.TP +.B \fBexample\fP: +bridge\-mcquerier 0 +.UNINDENT +.UNINDENT +.UNINDENT +.sp +\fBbridge\-mclmc\fP +.INDENT 0.0 +.INDENT 3.5 +\fBhelp\fP: set multicast last member count +.sp +\fBrequired\fP: False +.sp +\fBdefault\fP: 2 +.INDENT 0.0 +.TP +.B \fBexample\fP: +bridge\-mclmc 2 +.UNINDENT +.UNINDENT +.UNINDENT +.sp +\fBbridge\-mcrouter\fP +.INDENT 0.0 +.INDENT 3.5 +\fBhelp\fP: set multicast router +.sp +\fBrequired\fP: False +.sp +\fBdefault\fP: 1 +.INDENT 0.0 +.TP +.B \fBexample\fP: +bridge\-mcrouter 1 +.UNINDENT +.UNINDENT +.UNINDENT +.sp +\fBbridge\-portmcrouter\fP +.INDENT 0.0 +.INDENT 3.5 +\fBhelp\fP: set port multicast routers +.sp +\fBrequired\fP: False +.sp +\fBdefault\fP: 1 +.INDENT 0.0 +.TP +.B \fBexample\fP: +bridge\-portmcrouter swp1=1 swp2=1 +.UNINDENT +.UNINDENT +.UNINDENT +.sp +\fBbridge\-mclmi\fP +.INDENT 0.0 +.INDENT 3.5 +\fBhelp\fP: set multicast last member interval (in secs) +.sp +\fBrequired\fP: False +.sp +\fBdefault\fP: 1 +.INDENT 0.0 +.TP +.B \fBexample\fP: +bridge\-mclmi 1 +.UNINDENT +.UNINDENT +.UNINDENT +.sp +\fBbridge\-hashmax\fP +.INDENT 0.0 +.INDENT 3.5 +\fBhelp\fP: set hash max +.sp +\fBrequired\fP: False +.sp +\fBdefault\fP: 4096 +.INDENT 0.0 +.TP +.B \fBexample\fP: +bridge\-hashmax 4096 +.UNINDENT +.UNINDENT +.UNINDENT +.sp +\fBbridge\-waitport\fP +.INDENT 0.0 +.INDENT 3.5 +\fBhelp\fP: wait for a max of time secs for the specified ports +to become available,if no ports are specified then those speci +fied on bridge\-ports will be used here. Specifying no ports he +re should not be used if we are using regex or "all" on bridge +_ports,as it wouldnt work. +.sp +\fBrequired\fP: False +.sp +\fBdefault\fP: 0 +.INDENT 0.0 +.TP +.B \fBexample\fP: +bridge\-waitport 4 +.UNINDENT +.UNINDENT +.UNINDENT +.sp +\fBbridge\-mcqri\fP +.INDENT 0.0 +.INDENT 3.5 +\fBhelp\fP: set multicast query response interval (in secs) +.sp +\fBrequired\fP: False +.sp +\fBdefault\fP: 10 +.INDENT 0.0 +.TP +.B \fBexample\fP: +bridge\-mcqri 10 +.UNINDENT +.UNINDENT +.UNINDENT +.sp +\fBbridge\-hashel\fP +.INDENT 0.0 +.INDENT 3.5 +\fBhelp\fP: set hash elasticity +.sp +\fBrequired\fP: False +.sp +\fBdefault\fP: 4096 +.INDENT 0.0 +.TP +.B \fBexample\fP: +bridge\-hashel 4096 +.UNINDENT +.UNINDENT +.UNINDENT +.sp +\fBbridge\-mcqpi\fP +.INDENT 0.0 +.INDENT 3.5 +\fBhelp\fP: set multicast querier interval (in secs) +.sp +\fBrequired\fP: False +.sp +\fBdefault\fP: 255 +.INDENT 0.0 +.TP +.B \fBexample\fP: +bridge\-mcqpi 255 +.UNINDENT +.UNINDENT +.UNINDENT +.sp +\fBbridge\-bridgeprio\fP +.INDENT 0.0 +.INDENT 3.5 +\fBhelp\fP: bridge priority +.sp +\fBrequired\fP: False +.sp +\fBdefault\fP: 32768 +.INDENT 0.0 +.TP +.B \fBexample\fP: +bridge\-bridgeprio 32768 +.UNINDENT +.UNINDENT +.UNINDENT +.sp +\fBbridge\-maxage\fP +.INDENT 0.0 +.INDENT 3.5 +\fBhelp\fP: bridge set maxage +.sp +\fBrequired\fP: False +.sp +\fBdefault\fP: 20 +.INDENT 0.0 +.TP +.B \fBexample\fP: +bridge\-maxage 20 +.UNINDENT +.UNINDENT +.UNINDENT +.sp +\fBbridge\-portmcfl\fP +.INDENT 0.0 +.INDENT 3.5 +\fBhelp\fP: port multicast fast leave +.sp +\fBrequired\fP: False +.sp +\fBdefault\fP: 0 +.INDENT 0.0 +.TP +.B \fBexample\fP: +bridge\-portmcfl swp1=0 swp2=0 +.UNINDENT +.UNINDENT +.UNINDENT +.sp +\fBbridge\-mcqi\fP +.INDENT 0.0 +.INDENT 3.5 +\fBhelp\fP: set multicast query interval (in secs) +.sp +\fBrequired\fP: False +.sp +\fBdefault\fP: 125 +.INDENT 0.0 +.TP +.B \fBexample\fP: +bridge\-mcqi 125 +.UNINDENT +.UNINDENT +.UNINDENT +.UNINDENT +.UNINDENT +.sp +\fBusercmds\fP: user commands for interfaces +.INDENT 0.0 +.INDENT 3.5 +\fBdown\fP +.INDENT 0.0 +.INDENT 3.5 +\fBhelp\fP: run command at interface down +.sp +\fBrequired\fP: False +.UNINDENT +.UNINDENT +.sp +\fBpost\-up\fP +.INDENT 0.0 +.INDENT 3.5 +\fBhelp\fP: run command after interface bring up +.sp +\fBrequired\fP: False +.UNINDENT +.UNINDENT +.sp +\fBup\fP +.INDENT 0.0 +.INDENT 3.5 +\fBhelp\fP: run command at interface bring up +.sp +\fBrequired\fP: False +.UNINDENT +.UNINDENT +.sp +\fBpre\-down\fP +.INDENT 0.0 +.INDENT 3.5 +\fBhelp\fP: run command before bringing the interface down +.sp +\fBrequired\fP: False +.UNINDENT +.UNINDENT +.sp +\fBpre\-up\fP +.INDENT 0.0 +.INDENT 3.5 +\fBhelp\fP: run command before bringing the interface up +.sp +\fBrequired\fP: False +.UNINDENT +.UNINDENT +.sp +\fBpost\-down\fP +.INDENT 0.0 +.INDENT 3.5 +\fBhelp\fP: run command after bringing interface down +.sp +\fBrequired\fP: False +.UNINDENT +.UNINDENT +.UNINDENT +.UNINDENT +.sp +\fBmstpctl\fP: mstp configuration module for bridges +.INDENT 0.0 +.INDENT 3.5 +\fBmstpctl\-fdelay\fP +.INDENT 0.0 +.INDENT 3.5 +\fBhelp\fP: set forwarding delay +.sp +\fBrequired\fP: False +.sp +\fBdefault\fP: 15 +.INDENT 0.0 +.TP +.B \fBexample\fP: +mstpctl\-fdelay 15 +.UNINDENT +.UNINDENT +.UNINDENT +.sp +\fBmstpctl\-txholdcount\fP +.INDENT 0.0 +.INDENT 3.5 +\fBhelp\fP: bridge transmit holdcount +.sp +\fBrequired\fP: False +.sp +\fBdefault\fP: 6 +.INDENT 0.0 +.TP +.B \fBexample\fP: +mstpctl\-txholdcount 6 +.UNINDENT +.UNINDENT +.UNINDENT +.sp +\fBmstpctl\-portautoedge\fP +.INDENT 0.0 +.INDENT 3.5 +\fBhelp\fP: enable/disable auto transition to/from edge state of +the port +.sp +\fBrequired\fP: False +.sp +\fBdefault\fP: no +.sp +\fBvalidvals\fP: yes,no +.INDENT 0.0 +.TP +.B \fBexample\fP: +mstpctl\-portautoedge swp1=yes swp2=yes +.UNINDENT +.UNINDENT +.UNINDENT +.sp +\fBmstpctl\-portrestrrole\fP +.INDENT 0.0 +.INDENT 3.5 +\fBhelp\fP: enable/disable port ability to take root role of the +port +.sp +\fBrequired\fP: False +.sp +\fBdefault\fP: no +.sp +\fBvalidvals\fP: yes,no +.INDENT 0.0 +.TP +.B \fBexample\fP: +mstpctl\-portrestrrole swp1=no swp2=no +.UNINDENT +.UNINDENT +.UNINDENT +.sp +\fBmstpctl\-portnetwork\fP +.INDENT 0.0 +.INDENT 3.5 +\fBhelp\fP: enable/disable bridge assurance capability for a por +t +.sp +\fBrequired\fP: False +.sp +\fBdefault\fP: no +.sp +\fBvalidvals\fP: yes,no +.INDENT 0.0 +.TP +.B \fBexample\fP: +mstpctl\-portnetwork swp1=no swp2=no +.UNINDENT +.UNINDENT +.UNINDENT +.sp +\fBmstpctl\-portp2p\fP +.INDENT 0.0 +.INDENT 3.5 +\fBhelp\fP: bridge port p2p detection mode +.sp +\fBrequired\fP: False +.sp +\fBdefault\fP: no +.sp +\fBvalidvals\fP: yes,no +.INDENT 0.0 +.TP +.B \fBexample\fP: +mstpctl\-portp2p swp1=no swp2=no +.UNINDENT +.UNINDENT +.UNINDENT +.sp +\fBmstpctl\-treeprio\fP +.INDENT 0.0 +.INDENT 3.5 +\fBhelp\fP: tree priority +.sp +\fBrequired\fP: False +.sp +\fBdefault\fP: 32768 +.sp +validrange: 0\-65535 +.INDENT 0.0 +.TP +.B \fBexample\fP: +mstpctl\-treeprio 32768 +.UNINDENT +.UNINDENT +.UNINDENT +.sp +\fBmstpctl\-treeportprio\fP +.INDENT 0.0 +.INDENT 3.5 +\fBhelp\fP: port priority for MSTI instance +.sp +\fBrequired\fP: False +.sp +\fBdefault\fP: 128 +.sp +validrange: 0\-240 +.INDENT 0.0 +.TP +.B \fBexample\fP: +mstpctl\-treeportprio swp1=128 swp2=128 +.UNINDENT +.UNINDENT +.UNINDENT +.sp +\fBmstpctl\-hello\fP +.INDENT 0.0 +.INDENT 3.5 +\fBhelp\fP: set hello time +.sp +\fBrequired\fP: False +.sp +\fBdefault\fP: 2 +.INDENT 0.0 +.TP +.B \fBexample\fP: +mstpctl\-hello 2 +.UNINDENT +.UNINDENT +.UNINDENT +.sp +\fBmstpctl\-ageing\fP +.INDENT 0.0 +.INDENT 3.5 +\fBhelp\fP: ageing time +.sp +\fBrequired\fP: False +.sp +\fBdefault\fP: 300 +.INDENT 0.0 +.TP +.B \fBexample\fP: +mstpctl\-ageing 300 +.UNINDENT +.UNINDENT +.UNINDENT +.sp +\fBmstpctl\-portadminedge\fP +.INDENT 0.0 +.INDENT 3.5 +\fBhelp\fP: enable/disable initial edge state of the port +.sp +\fBrequired\fP: False +.sp +\fBdefault\fP: no +.sp +\fBvalidvals\fP: yes,no +.INDENT 0.0 +.TP +.B \fBexample\fP: +mstpctl\-portadminedge swp1=no swp2=no +.UNINDENT +.UNINDENT +.UNINDENT +.sp +\fBmstpctl\-maxage\fP +.INDENT 0.0 +.INDENT 3.5 +\fBhelp\fP: max message age +.sp +\fBrequired\fP: False +.sp +\fBdefault\fP: 20 +.INDENT 0.0 +.TP +.B \fBexample\fP: +mstpctl\-maxage 20 +.UNINDENT +.UNINDENT +.UNINDENT +.sp +\fBmstpctl\-maxhops\fP +.INDENT 0.0 +.INDENT 3.5 +\fBhelp\fP: bridge max hops +.sp +\fBrequired\fP: False +.sp +\fBdefault\fP: 15 +.INDENT 0.0 +.TP +.B \fBexample\fP: +mstpctl\-maxhops 15 +.UNINDENT +.UNINDENT +.UNINDENT +.sp +\fBmstpctl\-portrestrtcn\fP +.INDENT 0.0 +.INDENT 3.5 +\fBhelp\fP: enable/disable port ability to propagate received to +pology change notification of the port +.sp +\fBrequired\fP: False +.sp +\fBdefault\fP: no +.sp +\fBvalidvals\fP: yes,no +.INDENT 0.0 +.TP +.B \fBexample\fP: +mstpctl\-portrestrtcn swp1=no swp2=no +.UNINDENT +.UNINDENT +.UNINDENT +.sp +\fBmstpctl\-portpathcost\fP +.INDENT 0.0 +.INDENT 3.5 +\fBhelp\fP: bridge port path cost +.sp +\fBrequired\fP: False +.sp +\fBdefault\fP: 0 +.INDENT 0.0 +.TP +.B \fBexample\fP: +mstpctl\-portpathcost swp1=0 swp2=1 +.UNINDENT +.UNINDENT +.UNINDENT +.sp +\fBmstpctl\-portadminage\fP +.INDENT 0.0 +.INDENT 3.5 +\fBhelp\fP: bridge port admin age +.sp +\fBrequired\fP: False +.sp +\fBdefault\fP: no +.sp +\fBvalidvals\fP: yes,no +.INDENT 0.0 +.TP +.B \fBexample\fP: +mstpctl\-portadminage swp1=no swp2=no +.UNINDENT +.UNINDENT +.UNINDENT +.sp +\fBmstpctl\-portbpdufilter\fP +.INDENT 0.0 +.INDENT 3.5 +\fBhelp\fP: enable/disable bpdu filter on a port +.sp +\fBrequired\fP: False +.sp +\fBdefault\fP: no +.sp +\fBvalidvals\fP: yes,no +.INDENT 0.0 +.TP +.B \fBexample\fP: +mstpctl\-portbpdufilter swp1=no swp2=no +.UNINDENT +.UNINDENT +.UNINDENT +.sp +\fBmstpctl\-forcevers\fP +.INDENT 0.0 +.INDENT 3.5 +\fBhelp\fP: bridge force stp version +.sp +\fBrequired\fP: False +.sp +\fBdefault\fP: rstp +.INDENT 0.0 +.TP +.B \fBexample\fP: +mstpctl\-forcevers rstp +.UNINDENT +.UNINDENT +.UNINDENT +.sp +\fBmstpctl\-treeportcost\fP +.INDENT 0.0 +.INDENT 3.5 +\fBhelp\fP: port tree cost +.sp +\fBrequired\fP: False +.UNINDENT +.UNINDENT +.sp +\fBmstpctl\-bpduguard\fP +.INDENT 0.0 +.INDENT 3.5 +\fBhelp\fP: enable/disable bpduguard +.sp +\fBrequired\fP: False +.sp +\fBdefault\fP: no +.sp +\fBvalidvals\fP: yes,no +.INDENT 0.0 +.TP +.B \fBexample\fP: +mstpctl\-bpduguard swp1=no swp2=no +.UNINDENT +.UNINDENT +.UNINDENT +.UNINDENT +.UNINDENT +.sp +\fBvlan\fP: vlan module configures vlan interfaces.This module under +stands vlan interfaces with dot notations. eg swp1.100. Vlan inter +faces with any other names need to have raw device and vlan id att +ributes +.INDENT 0.0 +.INDENT 3.5 +\fBvlan\-id\fP +.INDENT 0.0 +.INDENT 3.5 +\fBhelp\fP: vlan id +.sp +\fBrequired\fP: False +.UNINDENT +.UNINDENT +.sp +\fBvlan\-raw\-device\fP +.INDENT 0.0 +.INDENT 3.5 +\fBhelp\fP: vlan raw device +.sp +\fBrequired\fP: False +.UNINDENT +.UNINDENT +.UNINDENT +.UNINDENT +.sp +\fBifenslave\fP: bond configuration module +.INDENT 0.0 +.INDENT 3.5 +\fBbond\-miimon\fP +.INDENT 0.0 +.INDENT 3.5 +\fBhelp\fP: bond miimon +.sp +\fBrequired\fP: False +.sp +\fBdefault\fP: 0 +.sp +validrange: 0\-255 +.INDENT 0.0 +.TP +.B \fBexample\fP: +bond\-miimon 0 +.UNINDENT +.UNINDENT +.UNINDENT +.sp +\fBbond\-slaves\fP +.INDENT 0.0 +.INDENT 3.5 +\fBhelp\fP: bond slaves +.sp +\fBrequired\fP: True +.INDENT 0.0 +.TP +.B \fBexample\fP: +bond\-slaves swp1 swp2 +.sp +bond\-slaves glob swp1\-2 +.sp +bond\-slaves regex (swp[1|2) +.UNINDENT +.UNINDENT +.UNINDENT +.sp +\fBbond\-mode\fP +.INDENT 0.0 +.INDENT 3.5 +\fBhelp\fP: bond mode +.sp +\fBrequired\fP: False +.sp +\fBdefault\fP: balance\-rr +.sp +\fBvalidvals\fP: balance\-rr,active\-backup,balance\-xor,broadcast,802.3ad,balance\-tlb,balance\-alb +.INDENT 0.0 +.TP +.B \fBexample\fP: +bond\-mode 802.3ad +.UNINDENT +.UNINDENT +.UNINDENT +.sp +\fBbond\-num\-grat\-arp\fP +.INDENT 0.0 +.INDENT 3.5 +\fBhelp\fP: bond use carrier +.sp +\fBrequired\fP: False +.sp +\fBdefault\fP: 1 +.sp +validrange: 0\-255 +.INDENT 0.0 +.TP +.B \fBexample\fP: +bond\-num\-grat\-arp 1 +.UNINDENT +.UNINDENT +.UNINDENT +.sp +\fBbond\-ad\-sys\-mac\-addr\fP +.INDENT 0.0 +.INDENT 3.5 +\fBhelp\fP: 802.3ad system mac address +.sp +\fBrequired\fP: False +.sp +\fBdefault\fP: 00:00:00:00:00:00 +.INDENT 0.0 +.TP +.B \fBexample\fP: +bond\-ad\-sys\-mac\-addr 00:00:00:00:00:00 +.UNINDENT +.UNINDENT +.UNINDENT +.sp +\fBbond\-use\-carrier\fP +.INDENT 0.0 +.INDENT 3.5 +\fBhelp\fP: bond use carrier +.sp +\fBrequired\fP: False +.sp +\fBdefault\fP: 1 +.sp +\fBvalidvals\fP: 0,1 +.INDENT 0.0 +.TP +.B \fBexample\fP: +bond\-use\-carrier 1 +.UNINDENT +.UNINDENT +.UNINDENT +.sp +\fBbond\-lacp\-rate\fP +.INDENT 0.0 +.INDENT 3.5 +\fBhelp\fP: bond use carrier +.sp +\fBrequired\fP: False +.sp +\fBdefault\fP: 0 +.sp +\fBvalidvals\fP: 0,1 +.INDENT 0.0 +.TP +.B \fBexample\fP: +bond\-lacp\-rate 0 +.UNINDENT +.UNINDENT +.UNINDENT +.sp +\fBbond\-min\-links\fP +.INDENT 0.0 +.INDENT 3.5 +\fBhelp\fP: bond min links +.sp +\fBrequired\fP: False +.sp +\fBdefault\fP: 0 +.INDENT 0.0 +.TP +.B \fBexample\fP: +bond\-min\-links 0 +.UNINDENT +.UNINDENT +.UNINDENT +.sp +\fBbond\-num\-unsol\-na\fP +.INDENT 0.0 +.INDENT 3.5 +\fBhelp\fP: bond slave devices +.sp +\fBrequired\fP: False +.sp +\fBdefault\fP: 1 +.sp +validrange: 0\-255 +.INDENT 0.0 +.TP +.B \fBexample\fP: +bond\-num\-unsol\-na 1 +.UNINDENT +.UNINDENT +.UNINDENT +.sp +\fBbond\-ad\-sys\-priority\fP +.INDENT 0.0 +.INDENT 3.5 +\fBhelp\fP: 802.3ad system priority +.sp +\fBrequired\fP: False +.sp +\fBdefault\fP: 65535 +.INDENT 0.0 +.TP +.B \fBexample\fP: +bond\-ad\-sys\-priority 65535 +.UNINDENT +.UNINDENT +.UNINDENT +.sp +\fBbond\-xmit\-hash\-policy\fP +.INDENT 0.0 +.INDENT 3.5 +\fBhelp\fP: bond slave devices +.sp +\fBrequired\fP: False +.sp +\fBdefault\fP: layer2 +.sp +\fBvalidvals\fP: layer2,layer3+4,layer2+3 +.INDENT 0.0 +.TP +.B \fBexample\fP: +bond\-xmit\-hash\-policy layer2 +.UNINDENT +.UNINDENT +.UNINDENT +.UNINDENT +.UNINDENT +.sp +\fBaddress\fP: address configuration module for interfaces +.INDENT 0.0 +.INDENT 3.5 +\fBbroadcast\fP +.INDENT 0.0 +.INDENT 3.5 +\fBhelp\fP: broadcast address +.sp +\fBrequired\fP: False +.INDENT 0.0 +.TP +.B \fBexample\fP: +broadcast 10.0.1.255 +.UNINDENT +.UNINDENT +.UNINDENT +.sp +\fBhwaddress\fP +.INDENT 0.0 +.INDENT 3.5 +\fBhelp\fP: hw address +.sp +\fBrequired\fP: False +.INDENT 0.0 +.TP +.B \fBexample\fP: +hwaddress 44:38:39:00:27:b8 +.UNINDENT +.UNINDENT +.UNINDENT +.sp +\fBalias\fP +.INDENT 0.0 +.INDENT 3.5 +\fBhelp\fP: description/alias +.sp +\fBrequired\fP: False +.INDENT 0.0 +.TP +.B \fBexample\fP: +alias testnetwork +.UNINDENT +.UNINDENT +.UNINDENT +.sp +\fBaddress\fP +.INDENT 0.0 +.INDENT 3.5 +\fBhelp\fP: ipv4 or ipv6 addresses +.sp +\fBrequired\fP: False +.INDENT 0.0 +.TP +.B \fBexample\fP: +address 10.0.12.3/24 +.sp +address 2000:1000:1000:1000:3::5/128 +.UNINDENT +.UNINDENT +.UNINDENT +.sp +\fBscope\fP +.INDENT 0.0 +.INDENT 3.5 +\fBhelp\fP: scope +.sp +\fBrequired\fP: False +.INDENT 0.0 +.TP +.B \fBexample\fP: +scope host +.UNINDENT +.UNINDENT +.UNINDENT +.sp +\fBpreferred\-lifetime\fP +.INDENT 0.0 +.INDENT 3.5 +\fBhelp\fP: preferred lifetime +.sp +\fBrequired\fP: False +.INDENT 0.0 +.TP +.B \fBexample\fP: +preferred\-lifetime forever +.sp +preferred\-lifetime 10 +.UNINDENT +.UNINDENT +.UNINDENT +.sp +\fBgateway\fP +.INDENT 0.0 +.INDENT 3.5 +\fBhelp\fP: default gateway +.sp +\fBrequired\fP: False +.INDENT 0.0 +.TP +.B \fBexample\fP: +gateway 255.255.255.0 +.UNINDENT +.UNINDENT +.UNINDENT +.sp +\fBmtu\fP +.INDENT 0.0 +.INDENT 3.5 +\fBhelp\fP: interface mtu +.sp +\fBrequired\fP: False +.sp +\fBdefault\fP: 1500 +.INDENT 0.0 +.TP +.B \fBexample\fP: +mtu 1600 +.UNINDENT +.UNINDENT +.UNINDENT +.UNINDENT +.UNINDENT +.UNINDENT +.UNINDENT +.SH SEE ALSO +.INDENT 0.0 +.INDENT 3.5 +interfaces(5), +ifup(8), +ip(8), +mstpctl(8), +brctl(8), +ethtool(8) +.UNINDENT +.UNINDENT +.SH AUTHOR +roopa@cumulusnetworks.com +.SH COPYRIGHT +Copyright 2013 Cumulus Networks, Inc. All rights reserved. +.\" Generated by docutils manpage writer. +.\" +. diff --git a/ifupdown2/man/interfaces.5 b/ifupdown2/man/interfaces.5 new file mode 100644 index 0000000..68f7d48 --- /dev/null +++ b/ifupdown2/man/interfaces.5 @@ -0,0 +1,207 @@ +.\" Man page generated from reStructeredText. +. +.TH INTERFACES 5 "2014-02-05" "0.1" "" +.SH NAME +interfaces \- network interface configuration for ifupdown +. +.nr rst2man-indent-level 0 +. +.de1 rstReportMargin +\\$1 \\n[an-margin] +level \\n[rst2man-indent-level] +level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] +- +\\n[rst2man-indent0] +\\n[rst2man-indent1] +\\n[rst2man-indent2] +.. +.de1 INDENT +.\" .rstReportMargin pre: +. RS \\$1 +. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] +. nr rst2man-indent-level +1 +.\" .rstReportMargin post: +.. +.de UNINDENT +. RE +.\" indent \\n[an-margin] +.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] +.nr rst2man-indent-level -1 +.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] +.in \\n[rst2man-indent\\n[rst2man-indent-level]]u +.. +.SH DESCRIPTION +.INDENT 0.0 +.INDENT 3.5 +\fB/etc/network/interfaces\fP contains network interface configuration +information for the \fBifup(8)\fP, \fBifdown(8)\fP and \fBifquery(8)\fP commands. +.sp +This is where you configure how your system is connected to the network. +.sp +Lines starting with # are ignored. Note that end\-of\-line comments are +NOT supported, comments must be on a line of their own. +.sp +A line may be extended across multiple lines by making the last character +a backslash. +.sp +The file consists of zero or more "iface", "auto", "allow\-" +and "source" stanzas. Here is an example: +.sp +.nf +.ft C +auto lo eth0 +allow\-hotplug eth1 + +iface lo inet loopback + +source /etc/network/interfaces.d/bridges + +iface eth0 inet static + address 192.168.1.1/24 + up flush\-mail + +iface eth1 inet dhcp +.ft P +.fi +.sp +Lines beginning with the word "auto" are used to identify the physical +interfaces to be brought up when ifup is run with the \-a option. +(This option is used by the system boot scripts.) Physical interface names +should follow the word "auto" on the same line. There can be multiple +"auto" stanzas. +.sp +Lines beginning with "allow\-" are used to identify interfaces that +should be brought up automatically by various subsytems. This may be +done using a command such as "ifup \-\-allow=hotplug eth0 eth1", which +will only bring up eth0 or eth1 if it is listed in an "allow\-hotplug" +line. Note that "allow\-auto" and "auto" are synonyms. +.sp +Lines beginning with "source" are used to include stanzas from other +files, so configuration can be split into many files. The word "source" +is followed by the path of file to be sourced. Shell wildcards can be +used. Currently only supports absolute +path names. +.sp +iface is normally given a interface name as its first non\-option +argument. +.sp +The interface name is followed by the name of the address family that the +interface uses. This will be "inet" for TCP/IP networking and inet6 for +ipv6. Following that is the name of the method used to configure the +interface. +.sp +ifupdown supports iface stanzas without a family or a method. This enables +using the same stanza for inet and inet6 family addresses. And the method +defaults to "static" +.sp +Additional interface options/attributes can be given on subsequent lines +in the iface stanza. These options come from addon modules. see +\fBifupdown\-addons\-interfaces(5)\fP for these options. +.sp +example bridge interface with additional attributes listed in the +\fBifupdown\-addons\-interfaces(5)\fP man page: +.sp +.nf +.ft C +auto br0 +iface br0 + address 12.0.0.4/24 + address 2000:1000:1000:1000:3::5/128 + bridge\-ports swp1 swp2 swp3 + bridge\-stp on +.ft P +.fi +.sp +ifupdown supports python\-mako style templates in the interfaces file. +See examples section for details. +.sp +See \fB/usr/share/doc/python\-ifupdown2/examples/\fP for \fBinterfaces(5)\fP +file examples and interfaces file generation scripts. +.UNINDENT +.UNINDENT +.SH METHODS +.INDENT 0.0 +.INDENT 3.5 +Both \fBinet\fP and \fBinet6\fP address family interfaces can use the following +methods (However they are not required): +.INDENT 0.0 +.TP +.B The loopback Method +This method may be used to define the loopback interface. +.TP +.B The static Method +This method may be used to define ethernet interfaces with +statically allocated addresses. +.TP +.B The dhcp Method +This method may be used to obtain an address via DHCP. +.UNINDENT +.UNINDENT +.UNINDENT +.SH BUILTIN INTERFACES +.INDENT 0.0 +.INDENT 3.5 +\fBiface\fP sections for some interfaces like physical interfaces or vlan +interfaces in dot notation (like eth1.100) are understood by ifupdown. +These interfaces do not need an entry in the interfaces file if +they are dependents of other interfaces and dont need any specific +configurations like addresses etc. +.UNINDENT +.UNINDENT +.SH EXAMPLES +.INDENT 0.0 +.INDENT 3.5 +Sample /etc/network/interfaces file: +.sp +.nf +.ft C +auto lo +iface lo + address 192.168.2.0/24 + address 2001:dee:eeee:1::4/128 + +auto eth0 +iface eth0 inet dhcp + +auto eth1 +iface eth1 inet manual + address 192.168.2.0/24 + address 2001:dee:eeee:1::4/128 + +# source files from a directory /etc/network/interfaces.d +source /etc/network/interfaces.d/* + +# Using mako style templates +% for v in [11,12]: + auto vlan${v} + iface vlan${v} inet static + address 10.20.${v}.3/24 +% endfor +.ft P +.fi +.sp +For additional syntax and examples see \fBifupdown\-addons\-interfaces(5)\fP +.UNINDENT +.UNINDENT +.SH FILES +.INDENT 0.0 +.INDENT 3.5 +/etc/network/interfaces +.UNINDENT +.UNINDENT +.SH SEE ALSO +.INDENT 0.0 +.INDENT 3.5 +ifupdown\-addons\-interfaces(5), +ifup(8), +ifquery(8), +ifreload(8) +.UNINDENT +.UNINDENT +.SH AUTHOR +Roopa Prabhu +.SH COPYRIGHT +Copyright 2014 Cumulus Networks, Inc. All rights reserved. +.\" Generated by docutils manpage writer. +.\" +. diff --git a/ifupdown2/sbin/ifupdown b/ifupdown2/sbin/ifupdown index 6ead2ed..d6bdeb2 100755 --- a/ifupdown2/sbin/ifupdown +++ b/ifupdown2/sbin/ifupdown @@ -13,6 +13,8 @@ import argparse import ConfigParser import StringIO import logging +import logging.handlers +import resource from ifupdown.ifupdownmain import * from ifupdown.utils import * @@ -49,13 +51,15 @@ def run_up(args): ifupdown_handle.up(['up'], args.all, args.CLASS, iflist, excludepats=args.excludepats, printdependency=args.printdependency, - syntaxcheck=args.syntaxcheck) + syntaxcheck=args.syntaxcheck, type=args.type, + skipupperifaces=args.skipupperifaces) else: ifupdown_handle.up(['pre-up', 'up', 'post-up'], args.all, args.CLASS, iflist, excludepats=args.excludepats, printdependency=args.printdependency, - syntaxcheck=args.syntaxcheck) + syntaxcheck=args.syntaxcheck, type=args.type, + skipupperifaces=args.skipupperifaces) except: raise @@ -79,7 +83,8 @@ def run_down(args): args.all, args.CLASS, iflist, excludepats=args.excludepats, printdependency=args.printdependency, - usecurrentconfig=args.usecurrentconfig) + usecurrentconfig=args.usecurrentconfig, + type=args.type) except: raise @@ -121,7 +126,7 @@ def run_query(args): ifupdown_handle.query([qop], args.all, args.CLASS, iflist, excludepats=args.excludepats, printdependency=args.printdependency, - format=args.format) + format=args.format, type=args.type) except: raise @@ -135,9 +140,10 @@ def run_reload(args): perfmode=args.perfmode) ifupdown_handle.reload(['pre-up', 'up', 'post-up'], ['pre-down', 'down', 'post-down'], - args.all, None, None, + auto=args.all, allow=None, ifacenames=None, excludepats=args.excludepats, - usecurrentconfig=args.usecurrentconfig) + usecurrentconfig=args.usecurrentconfig, + currentlyup=args.currentlyup) except: raise @@ -152,13 +158,27 @@ def init(args): log_level = logging.DEBUG try: - logging.basicConfig(level=log_level, - format='%(levelname)s: %(message)s') - logging.addLevelName(logging.ERROR, 'error') - logging.addLevelName(logging.WARNING, 'warning') - logging.addLevelName(logging.DEBUG, 'debug') - logging.addLevelName(logging.INFO, 'info') - logger = logging.getLogger('ifupdown') + if hasattr(args, 'syslog') and args.syslog: + root_logger = logging.getLogger() + syslog_handler = logging.handlers.SysLogHandler(address='/dev/log', + facility=logging.handlers.SysLogHandler.LOG_DAEMON) + logging.addLevelName(logging.ERROR, 'error') + logging.addLevelName(logging.WARNING, 'warning') + logging.addLevelName(logging.DEBUG, 'debug') + logging.addLevelName(logging.INFO, 'info') + root_logger.setLevel(log_level) + syslog_handler.setFormatter(logging.Formatter( + '%(name)s: %(levelname)s: %(message)s')) + root_logger.addHandler(syslog_handler) + logger = logging.getLogger('ifupdown') + else: + logging.basicConfig(level=log_level, + format='%(levelname)s: %(message)s') + logging.addLevelName(logging.ERROR, 'error') + logging.addLevelName(logging.WARNING, 'warning') + logging.addLevelName(logging.DEBUG, 'debug') + logging.addLevelName(logging.INFO, 'info') + logger = logging.getLogger('ifupdown') except: raise @@ -210,12 +230,22 @@ def update_argparser(argparser): default='native', choices=['native', 'json'], help='interfaces file format') + argparser.add_argument('-T', '--type', + dest='type', + default=None, + choices=['iface', 'vlan'], + help='type of interface entry (iface or vlan).' + + 'This option can be used in case of ambiguity between ' + + 'a vlan interface and an iface interface of the same name') def update_ifupdown_argparser(argparser): """ common arg parser for ifup and ifdown """ argparser.add_argument('-f', '--force', dest='force', action='store_true', help='force run all operations') + argparser.add_argument('-l', '--syslog', dest='syslog', + action='store_true', + help=argparse.SUPPRESS) group = argparser.add_mutually_exclusive_group(required=False) group.add_argument('-n', '--no-act', dest='noact', action='store_true', help='print out what would happen,' + @@ -232,6 +262,13 @@ def update_ifup_argparser(argparser): argparser.add_argument('-s', '--syntax-check', dest='syntaxcheck', action='store_true', help='Only run the interfaces file parser') + argparser.add_argument('-k', '--skip-upperifaces', dest='skipupperifaces', + action='store_true', + help='ifup by default tries to add newly created interfaces' + + ' into its upper/parent interfaces. Eg. if a bridge port is' + + ' created as a result of ifup on the port, ifup automatically' + + ' adds the port to the bridge. This option can be used to ' + + 'disable this default behaviour') update_ifupdown_argparser(argparser) def update_ifdown_argparser(argparser): @@ -275,8 +312,13 @@ def update_ifquery_argparser(argparser): def update_ifreload_argparser(argparser): """ parser for ifreload """ - argparser.add_argument('-a', '--all', action='store_true', required=True, + group = argparser.add_mutually_exclusive_group(required=True) + group.add_argument('-a', '--all', action='store_true', help='process all interfaces marked \"auto\"') + group.add_argument('-c', '--currently-up', dest='currentlyup', + action='store_true', + help='only reload auto and other interfaces that are ' + + 'currently up') argparser.add_argument('iflist', metavar='IFACE', nargs='*', help=argparse.SUPPRESS) argparser.add_argument('-n', '--no-act', dest='noact', @@ -308,6 +350,12 @@ def update_ifreload_argparser(argparser): ' only look at the current interfaces file. Useful when your ' + 'state file is corrupted or you want down to use the latest ' 'from the interfaces file') + argparser.add_argument('-l', '--syslog', dest='syslog', + action='store_true', + help=argparse.SUPPRESS) + argparser.add_argument('-f', '--force', dest='force', + action='store_true', + help='force run all operations') def parse_args(argsv, op): if op == 'query': @@ -345,8 +393,8 @@ def validate_args(op, args): if op == 'query' and args.syntaxhelp: return True if op == 'reload': - if not args.all: - print '\'-a\' option is required' + if not args.all and not args.currentlyup: + print '\'-a\' or \'-c\' option is required' return False elif (not args.iflist and not args.all and not args.CLASS): @@ -361,7 +409,7 @@ def validate_args(op, args): return False return True -def read_config(): +def read_config(args): global configmap_g config = open(configfile, 'r').read() @@ -371,6 +419,26 @@ def read_config(): parser.readfp(configFP) configmap_g = dict(parser.items('ifupdown2')) + # Preprocess config map + configval = configmap_g.get('multiple_vlan_aware_bridge_support', '0') + if configval == '0': + # if multiple bridges not allowed, set the bridge-vlan-aware + # attribute in the 'no_repeats' config, so that the ifupdownmain + # module can catch it appropriately + configmap_g['no_repeats'] = {'bridge-vlan-aware' : 'yes'} + + + configval = configmap_g.get('link_master_slave', '0') + if configval == '1': + # link_master_slave is only valid when all is set + if hasattr(args, 'all') and not args.all: + configmap_g['link_master_slave'] = '0' + + configval = configmap_g.get('delay_admin_state_change', '0') + if configval == '1': + # reset link_master_slave if delay_admin_state_change is on + configmap_g['link_master_slave'] = '0' + def main(argv): """ main function """ args = None @@ -401,7 +469,7 @@ def main(argv): print 'Another instance of this program is already running.' exit(0) - read_config() + read_config(args) init(args) handlers.get(op)(args) except Exception, e: @@ -422,7 +490,7 @@ def main(argv): if __name__ == "__main__": - # required during boot os.putenv('PATH', ENVPATH) + resource.setrlimit(resource.RLIMIT_CORE, (0,0)) main(sys.argv) diff --git a/ifupdown2/setup.py b/ifupdown2/setup.py index b6ca032..58218e3 100755 --- a/ifupdown2/setup.py +++ b/ifupdown2/setup.py @@ -6,23 +6,40 @@ setup(name='ifupdown2', author='Roopa Prabhu', author_email='roopa@cumulusnetworks.com', url='cumulusnetworks.com', - packages=['ifupdown'], + packages=['ifupdown', 'ifupdownaddons'], scripts = ['sbin/ifupdown'], - install_requires = ['python-gvgen'], + install_requires = ['python-gvgen', 'python-argcomplete', 'python-ipaddr'], data_files=[('share/man/man8/', ['man/ifup.8', 'man/ifquery.8', 'man/ifreload.8']), ('share/man/man5/', - ['man/interfaces.5']), + ['man/interfaces.5', 'man/ifupdown-addons-interfaces.5']), ('/etc/init.d/', ['init.d/networking']), ('/sbin/', ['sbin/ifupdown']), ('/etc/network/ifupdown2/', ['config/ifupdown2.conf']), + ('/etc/default/', + ['config/networking']), ('/usr/share/python-ifupdown2/', ['docs/examples/generate_interfaces.py']), ('/usr/share/doc/python-ifupdown2/examples/', ['docs/examples/interfaces', 'docs/examples/interfaces_bridge_template_func', - 'docs/examples/interfaces_with_template']), - ('/etc/bash_completion.d/', ['completion/ifup'])] + 'docs/examples/interfaces_with_template', + 'docs/examples/interfaces_bridge_igmp_mstp']), + ('/usr/share/doc/python-ifupdown2/examples/vlan_aware_bridges', + ['docs/examples/vlan_aware_bridges/interfaces.basic', + 'docs/examples/vlan_aware_bridges/interfaces.vlan_prune_and_access_ports', + 'docs/examples/vlan_aware_bridges/interfaces.with_bonds', + 'docs/examples/vlan_aware_bridges/interfaces.with_clag']), + ('/etc/bash_completion.d/', ['completion/ifup']), + ('/usr/share/ifupdownaddons/', ['addons/bridge.py', + 'addons/ifenslave.py', 'addons/vlan.py', + 'addons/mstpctl.py', 'addons/address.py', + 'addons/dhcp.py', 'addons/usercmds.py', + 'addons/ethtool.py', 'addons/loopback.py', + 'addons/addressvirtual.py', 'addons/vxlan.py', + 'addons/bridgevlan.py']), + ('/var/lib/ifupdownaddons/', ['config/addons.conf']) + ] )