diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7b55d73 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.pyc +*.swp +build diff --git a/TODO.addons b/TODO.addons new file mode 100644 index 0000000..5436284 --- /dev/null +++ b/TODO.addons @@ -0,0 +1,13 @@ +TODO: +==== +- run python code guideline checker +- more code documentation +- move all cache handling to decorators in ifupdownaddons package +- input validation (present in some cases not all) +- support the vlan0004 style vlans +- ifquery coverage. currently it is at 80%. +- vxlan module +- fix and release ifaddon utility to manage module priorities +- Deep compare in query for address module (does not compare address attributes like scope) +- Maybe a pure netlink backend +- improve caching diff --git a/addons/__init__.py b/addons/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/addons/address.py b/addons/address.py index 310cc87..661eae0 100644 --- a/addons/address.py +++ b/addons/address.py @@ -4,13 +4,19 @@ # Author: Roopa Prabhu, roopa@cumulusnetworks.com # +import os + try: - from ipaddr import IPNetwork + from ipaddr import IPNetwork, IPv4Network, IPv6Network, IPv4Address, IPv6Address from sets import Set from ifupdown.iface import * from ifupdownaddons.modulebase import moduleBase from ifupdownaddons.iproute2 import iproute2 from ifupdownaddons.dhclient import dhclient + import ifupdown.policymanager as policymanager + from ifupdown.netlink import netlink + import ifupdown.ifupdownconfig as ifupdownConfig + import ifupdown.ifupdownflags as ifupdownflags except ImportError, e: raise ImportError (str(e) + "- required module not found") @@ -22,34 +28,44 @@ class address(moduleBase): 'attrs': { 'address' : {'help' : 'ipv4 or ipv6 addresses', + 'validvals' : [IPv4Network, IPv6Network], + 'multiline' : True, 'example' : ['address 10.0.12.3/24', 'address 2000:1000:1000:1000:3::5/128']}, 'netmask' : {'help': 'netmask', + 'validvals' : [IPv4Address, ], 'example' : ['netmask 255.255.255.0'], 'compat' : True}, 'broadcast' : {'help': 'broadcast address', + 'validvals' : [IPv4Address, ], 'example' : ['broadcast 10.0.1.255']}, 'scope' : {'help': 'scope', + 'validvals' : ['universe', 'site', 'link', 'host', 'nowhere'], 'example' : ['scope host']}, 'preferred-lifetime' : {'help': 'preferred lifetime', + 'validrange' : ['0', '65535'], 'example' : ['preferred-lifetime forever', 'preferred-lifetime 10']}, 'gateway' : {'help': 'default gateway', + 'validvals' : [IPv4Address, IPv6Address], 'example' : ['gateway 255.255.255.0']}, 'mtu' : { 'help': 'interface mtu', + 'validrange' : ['552', '9216'], 'example' : ['mtu 1600'], 'default' : '1500'}, 'hwaddress' : {'help' : 'hw address', + 'validvals' : ['',], 'example': ['hwaddress 44:38:39:00:27:b8']}, 'alias' : { 'help': 'description/alias', + 'validvals' : ['',], 'example' : ['alias testnetwork']}, 'address-purge' : { 'help': 'purge existing addresses. By default ' + @@ -57,13 +73,20 @@ class address(moduleBase): 'purged to match persistant addresses in the ' + 'interfaces file. Set this attribute to \'no\'' + 'if you want to preserve existing addresses', + 'validvals' : ['yes', 'no'], 'default' : 'yes', - 'example' : ['address-purge yes/no']}}} + 'example' : ['address-purge yes/no']}, + 'clagd-vxlan-anycast-ip' : + { 'help' : 'Anycast local IP address for ' + + 'dual connected VxLANs', + 'validvals' : [IPv4Address, ], + 'example' : ['clagd-vxlan-anycast-ip 36.0.0.11']}}} def __init__(self, *args, **kargs): moduleBase.__init__(self, *args, **kargs) self.ipcmd = None self._bridge_fdb_query_cache = {} + self.default_mtu = policymanager.policymanager_api.get_attr_default(module_name=self.__class__.__name__, attr='mtu') def _address_valid(self, addrs): if not addrs: @@ -75,7 +98,7 @@ class address(moduleBase): def _get_hwaddress(self, ifaceobj): hwaddress = ifaceobj.get_attr_value_first('hwaddress') - if hwaddress and hwaddress.startswith('ether'): + if hwaddress and hwaddress.startswith("ether"): hwaddress = hwaddress[5:].strip() return hwaddress @@ -103,40 +126,125 @@ class address(moduleBase): 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' + def _get_anycast_addr(self, ifaceobjlist): + for ifaceobj in ifaceobjlist: + anycast_addr = ifaceobj.get_attr_value_first('clagd-vxlan-anycast-ip') + if anycast_addr: + anycast_addr = anycast_addr+'/32' + return anycast_addr + return None + + def _inet_address_convert_to_cidr(self, ifaceobjlist): newaddrs = [] - addrs = ifaceobj.get_attr_value('address') - if addrs: - if ifaceobj.role & ifaceRole.SLAVE: - # we must not configure an IP address if the interface is enslaved - self.log_warn('interface %s is enslaved and cannot have an IP Address' % \ - (ifaceobj.name)) - return + newaddr_attrs = {} + + for ifaceobj in ifaceobjlist: + addrs = ifaceobj.get_attr_value('address') + if not addrs: + continue + + if (((ifaceobj.role & ifaceRole.SLAVE) and + not (ifaceobj.link_privflags & ifaceLinkPrivFlags.VRF_SLAVE)) or + ((ifaceobj.link_kind & ifaceLinkKind.BRIDGE) and + (ifaceobj.link_privflags & ifaceLinkPrivFlags.BRIDGE_VLAN_AWARE))): + # we must not configure an IP address if the interface is + # enslaved or is a VLAN AWARE BRIDGE + self.logger.info('%s: ignoring ip address. Interface is ' + 'enslaved or a vlan aware bridge and cannot' + ' have an IP Address' %(ifaceobj.name)) + return (False, newaddrs, newaddr_attrs) # 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 + newaddr = addr netmask = ifaceobj.get_attr_value_n('netmask', addr_index) if netmask: prefixlen = IPNetwork('%s' %addr + '/%s' %netmask).prefixlen - newaddrs.append(addr + '/%s' %prefixlen) + newaddr = addr + '/%s' %prefixlen else: - newaddrs.append(addr) + # we are here because there is no slash (/xx) and no netmask + # just let IPNetwork handle the ipv4 or ipv6 address mask + prefixlen = IPNetwork(addr).prefixlen + newaddr = addr + '/%s' %prefixlen + newaddrs.append(newaddr) - 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 + attrs = {} + for a in ['broadcast', 'pointopoint', 'scope', + 'preferred-lifetime']: + aval = ifaceobj.get_attr_value_n(a, addr_index) + if aval: + attrs[a] = aval + + if attrs: + newaddr_attrs[newaddr]= attrs + return (True, newaddrs, newaddr_attrs) + + def _inet_address_list_config(self, ifaceobj, newaddrs, newaddr_attrs): + for addr_index in range(0, len(newaddrs)): + try: + if newaddr_attrs: + self.ipcmd.addr_add(ifaceobj.name, newaddrs[addr_index], + newaddr_attrs.get(newaddrs[addr_index], + {}).get('broadcast'), + newaddr_attrs.get(newaddrs[addr_index], + {}).get('pointopoint'), + newaddr_attrs.get(newaddrs[addr_index], + {}).get('scope'), + newaddr_attrs.get(newaddrs[addr_index], + {}).get('preferred-lifetime')) + else: + self.ipcmd.addr_add(ifaceobj.name, newaddrs[addr_index]) + except Exception, e: + self.log_error(str(e), ifaceobj) + + def _inet_address_config(self, ifaceobj, ifaceobj_getfunc=None, + force_reapply=False): + squash_addr_config = (True if \ + ifupdownConfig.config.get('addr_config_squash', \ + '0') == '1' else False) + + if (squash_addr_config and + not (ifaceobj.flags & ifaceobj.YOUNGEST_SIBLING)): + return + + purge_addresses = ifaceobj.get_attr_value_first('address-purge') + if not purge_addresses: + purge_addresses = 'yes' + + if squash_addr_config and ifaceobj.flags & iface.HAS_SIBLINGS: + ifaceobjlist = ifaceobj_getfunc(ifaceobj.name) + else: + ifaceobjlist = [ifaceobj] + + (addr_supported, newaddrs, newaddr_attrs) = self._inet_address_convert_to_cidr(ifaceobjlist) + if not addr_supported: + return + if (not squash_addr_config and (ifaceobj.flags & iface.HAS_SIBLINGS)): + # if youngest sibling and squash addr is not set + # print a warning that addresses will not be purged + if (ifaceobj.flags & iface.YOUNGEST_SIBLING): + self.logger.warn('%s: interface has multiple ' %ifaceobj.name + + 'iface stanzas, skip purging existing addresses') + purge_addresses = 'no' + + if not ifupdownflags.flags.PERFMODE and purge_addresses == 'yes': + # if perfmode is not set and purge addresses is not set to 'no' + # lets purge addresses not in the config runningaddrs = self.ipcmd.addr_get(ifaceobj.name, details=False) + + # if anycast address is configured on 'lo' and is in running config + # add it to newaddrs so that ifreload doesn't wipe it out + anycast_addr = self._get_anycast_addr(ifaceobjlist) + + if runningaddrs and anycast_addr and anycast_addr in runningaddrs: + newaddrs.append(anycast_addr) if newaddrs == runningaddrs: + if force_reapply: + self._inet_address_list_config(ifaceobj, newaddrs, newaddr_attrs) return try: # if primary address is not same, there is no need to keep any. @@ -150,62 +258,93 @@ class address(moduleBase): 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)) + self._inet_address_list_config(ifaceobj, newaddrs, newaddr_attrs) - def _up(self, ifaceobj): + def _up(self, ifaceobj, ifaceobj_getfunc=None): if not self.ipcmd.link_exists(ifaceobj.name): return addr_method = ifaceobj.addr_method + force_reapply = False try: # release any stale dhcp addresses if present - if (addr_method != "dhcp" and not self.PERFMODE and + if (addr_method != "dhcp" and not ifupdownflags.flags.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): + if dhclientcmd.is_running(ifaceobj.name): # release any dhcp leases dhclientcmd.release(ifaceobj.name) - elif dhclient.is_running6(ifaceobj.name): + force_reapply = True + elif dhclientcmd.is_running6(ifaceobj.name): dhclientcmd.release6(ifaceobj.name) + force_reapply = True except: pass self.ipcmd.batch_start() if addr_method != "dhcp": - self._inet_address_config(ifaceobj) + self._inet_address_config(ifaceobj, ifaceobj_getfunc, + force_reapply) mtu = ifaceobj.get_attr_value_first('mtu') if mtu: self.ipcmd.link_set(ifaceobj.name, 'mtu', mtu) + elif (not ifaceobj.link_kind and + not (ifaceobj.link_privflags & ifaceLinkPrivFlags.BOND_SLAVE) and + self.default_mtu): + # logical devices like bridges and vlan devices rely on mtu + # from their lower devices. ie mtu travels from + # lower devices to upper devices. For bonds mtu travels from + # upper to lower devices. running mtu depends on upper and + # lower device mtu. With all this implicit mtu + # config by the kernel in play, we try to be cautious here + # on which devices we want to reset mtu to default. + # essentially only physical interfaces which are not bond slaves + running_mtu = self.ipcmd.link_get_mtu(ifaceobj.name) + if running_mtu != self.default_mtu: + self.ipcmd.link_set(ifaceobj.name, 'mtu', self.default_mtu) + alias = ifaceobj.get_attr_value_first('alias') if alias: self.ipcmd.link_set_alias(ifaceobj.name, alias) + self.ipcmd.batch_commit() + hwaddress = self._get_hwaddress(ifaceobj) if hwaddress: - self.ipcmd.link_set(ifaceobj.name, 'address', hwaddress) - self.ipcmd.batch_commit() + running_hwaddress = None + if not ifupdownflags.flags.PERFMODE: # system is clean + running_hwaddress = self.ipcmd.link_get_hwaddress(ifaceobj.name) + if hwaddress != running_hwaddress: + slave_down = False + netlink.link_set_updown(ifaceobj.name, "down") + if ifaceobj.link_kind & ifaceLinkKind.BOND: + # if bond, down all the slaves + if ifaceobj.lowerifaces: + for l in ifaceobj.lowerifaces: + netlink.link_set_updown(l, "down") + slave_down = True + try: + self.ipcmd.link_set(ifaceobj.name, 'address', hwaddress) + finally: + netlink.link_set_updown(ifaceobj.name, "up") + if slave_down: + for l in ifaceobj.lowerifaces: + netlink.link_set_updown(l, "up") try: # Handle special things on a bridge self._process_bridge(ifaceobj, True) except Exception, e: - self.log_warn('%s: %s' %(ifaceobj.name, str(e))) + self.log_error('%s: %s' %(ifaceobj.name, str(e)), ifaceobj) pass if addr_method != "dhcp": self.ipcmd.route_add_gateway(ifaceobj.name, - ifaceobj.get_attr_value_first('gateway')) + ifaceobj.get_attr_value_first('gateway'), + ifaceobj.get_attr_value_first('vrf')) - def _down(self, ifaceobj): + def _down(self, ifaceobj, ifaceobj_getfunc=None): try: if not self.ipcmd.link_exists(ifaceobj.name): return @@ -213,11 +352,24 @@ class address(moduleBase): if addr_method != "dhcp": self.ipcmd.route_del_gateway(ifaceobj.name, ifaceobj.get_attr_value_first('gateway'), + ifaceobj.get_attr_value_first('vrf'), ifaceobj.get_attr_value_first('metric')) - self.ipcmd.del_addr_all(ifaceobj.name) + if ifaceobj.get_attr_value_first('address-purge')=='no': + addrlist = ifaceobj.get_attr_value('address') + for addr in addrlist: + self.ipcmd.addr_del(ifaceobj.name, addr) + #self.ipcmd.addr_del(ifaceobj.name, ifaceobj.get_attr_value('address')[0]) + else: + self.ipcmd.del_addr_all(ifaceobj.name) + mtu = ifaceobj.get_attr_value_first('mtu') + if (not ifaceobj.link_kind and mtu and + self.default_mtu and (mtu != self.default_mtu)): + self.ipcmd.link_set(ifaceobj.name, 'mtu', self.default_mtu) alias = ifaceobj.get_attr_value_first('alias') if alias: - self.ipcmd.link_set(ifaceobj.name, 'alias', "\'\'") + filename = '/sys/class/net/%s/ifalias' %ifaceobj.name + self.logger.info('executing echo "" > %s' %filename) + os.system('echo "" > %s' %filename) # XXX hwaddress reset cannot happen because we dont know last # address. @@ -262,7 +414,7 @@ class address(moduleBase): return False return True - def _query_check(self, ifaceobj, ifaceobjcurr): + def _query_check(self, ifaceobj, ifaceobjcurr, ifaceobj_getfunc=None): runningaddrsdict = None if not self.ipcmd.link_exists(ifaceobj.name): self.logger.debug('iface %s not found' %ifaceobj.name) @@ -291,6 +443,13 @@ class address(moduleBase): return addrs = self._get_iface_addresses(ifaceobj) runningaddrsdict = self.ipcmd.addr_get(ifaceobj.name) + # if anycast address is configured on 'lo' and is in running config + # add it to addrs so that query_check doesn't fail + anycast_addr = ifaceobj.get_attr_value_first('clagd-vxlan-anycast-ip') + if anycast_addr: + anycast_addr = anycast_addr+'/32' + if runningaddrsdict and anycast_addr and runningaddrsdict.get(anycast_addr): + addrs.append(anycast_addr) # Set ifaceobjcurr method and family ifaceobjcurr.addr_method = ifaceobj.addr_method @@ -328,7 +487,7 @@ class address(moduleBase): #XXXX Check broadcast address, scope, etc return - def _query_running(self, ifaceobjrunning): + def _query_running(self, ifaceobjrunning, ifaceobj_getfunc=None): if not self.ipcmd.link_exists(ifaceobjrunning.name): self.logger.debug('iface %s not found' %ifaceobjrunning.name) return @@ -336,7 +495,7 @@ class address(moduleBase): if (dhclientcmd.is_running(ifaceobjrunning.name) or dhclientcmd.is_running6(ifaceobjrunning.name)): # If dhcp is configured on the interface, we skip it - return + return isloopback = self.ipcmd.link_isloopback(ifaceobjrunning.name) if isloopback: default_addrs = ['127.0.0.1/8', '::1/128'] @@ -356,7 +515,7 @@ class address(moduleBase): mtu != self.get_mod_subattr('mtu', 'default'))): ifaceobjrunning.update_config('mtu', mtu) alias = self.ipcmd.link_get_alias(ifaceobjrunning.name) - if alias: + if alias: ifaceobjrunning.update_config('alias', alias) _run_ops = {'up' : _up, @@ -370,9 +529,9 @@ class address(moduleBase): def _init_command_handlers(self): if not self.ipcmd: - self.ipcmd = iproute2(**self.get_flags()) + self.ipcmd = iproute2() - def run(self, ifaceobj, operation, query_ifaceobj=None, **extra_args): + def run(self, ifaceobj, operation, query_ifaceobj=None, ifaceobj_getfunc=None): """ run address configuration on the interface object passed as argument Args: @@ -395,6 +554,8 @@ class address(moduleBase): return self._init_command_handlers() if operation == 'query-checkcurr': - op_handler(self, ifaceobj, query_ifaceobj) + op_handler(self, ifaceobj, query_ifaceobj, + ifaceobj_getfunc=ifaceobj_getfunc) else: - op_handler(self, ifaceobj) + op_handler(self, ifaceobj, + ifaceobj_getfunc=ifaceobj_getfunc) diff --git a/addons/addressvirtual.py b/addons/addressvirtual.py index 98ac536..73f2d4c 100644 --- a/addons/addressvirtual.py +++ b/addons/addressvirtual.py @@ -7,8 +7,12 @@ 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 ifupdown.ifupdownconfig as ifupdownConfig +import ifupdown.statemanager as statemanager +from ifupdown.netlink import netlink +import ifupdown.ifupdownflags as ifupdownflags + +from ipaddr import IPNetwork, IPv4Network import logging import os import glob @@ -22,7 +26,8 @@ class addressvirtual(moduleBase): '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']} + 'validvals' : [('', IPv4Network), ], + 'example' : ['address-virtual 00:11:22:33:44:01 11.0.1.1/24 11.0.1.2/24']} }} @@ -52,21 +57,22 @@ class addressvirtual(moduleBase): def _remove_addresses_from_bridge(self, ifaceobj, hwaddress): # XXX: batch the addresses - bridgename = None if '.' in ifaceobj.name: + (bridgename, vlan) = ifaceobj.name.split('.') if self.ipcmd.bridge_is_vlan_aware(bridgename): - (bridgename, vlan) = ifaceobj.name.split('.') + 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 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 + for addr in hwaddress: + try: + self.ipcmd.bridge_fdb_del(ifaceobj.name, addr) + 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) @@ -119,8 +125,33 @@ class addressvirtual(moduleBase): %str(e)) pass + def _get_macs_from_old_config(self, ifaceobj=None): + """ This method returns a list of the mac addresses + in the address-virtual attribute for the bridge. """ + maclist = [] + saved_ifaceobjs = statemanager.statemanager_api.get_ifaceobjs(ifaceobj.name) + if not saved_ifaceobjs: + return maclist + # we need the old saved configs from the statemanager + for oldifaceobj in saved_ifaceobjs: + if not oldifaceobj.get_attr_value('address-virtual'): + continue + for av in oldifaceobj.get_attr_value('address-virtual'): + macip = av.split() + if len(macip) < 2: + self.logger.debug("%s: incorrect old address-virtual attrs '%s'" + %(oldifaceobj.name, av)) + continue + maclist.append(macip[0]) + return maclist + def _apply_address_config(self, ifaceobj, address_virtual_list): - purge_existing = False if self.PERFMODE else True + purge_existing = False if ifupdownflags.flags.PERFMODE else True + + lower_iface_mtu = update_mtu = None + if ifupdownConfig.config.get('adjust_logical_dev_mtu', '1') != '0': + if ifaceobj.lowerifaces and address_virtual_list: + update_mtu = True hwaddress = [] self.ipcmd.batch_start() @@ -129,31 +160,52 @@ class addressvirtual(moduleBase): 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)) + self.log_error("%s: incorrect address-virtual attrs '%s'" + %(ifaceobj.name, av), ifaceobj, + raise_error=False) av_idx += 1 continue + mac = av_attrs[0] + if not self.check_mac_address(ifaceobj, mac): + 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) + netlink.link_add_macvlan(ifaceobj.name, macvlan_ifacename) 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:], + ips = av_attrs[1:] + if mac != 'None': + mac = mac.lower() + # customer could have used UPPERCASE for MAC + self.ipcmd.link_set_hwaddress(macvlan_ifacename, mac) + hwaddress.append(mac) + self.ipcmd.addr_add_multiple(macvlan_ifacename, ips, purge_existing) + # If link existed before, flap the link if not link_created: self._fix_connected_route(ifaceobj, macvlan_ifacename, - av_attrs[1]) + ips[0]) + if update_mtu: + lower_iface_mtu = self.ipcmd.link_get_mtu(ifaceobj.lowerifaces[0], refresh=True) + update_mtu = False + + if lower_iface_mtu and lower_iface_mtu != self.ipcmd.link_get_mtu(macvlan_ifacename): + self.ipcmd.link_set_mtu(macvlan_ifacename, lower_iface_mtu) + av_idx += 1 self.ipcmd.batch_commit() + # check the statemanager for old configs. + # We need to remove only the previously configured FDB entries + oldmacs = self._get_macs_from_old_config(ifaceobj) + # get a list of fdbs in old that are not in new config meaning they should + # be removed since they are gone from the config + removed_macs = [mac for mac in oldmacs if mac.lower() not in hwaddress] + self._remove_addresses_from_bridge(ifaceobj, removed_macs) # 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) @@ -164,7 +216,7 @@ class addressvirtual(moduleBase): hwaddress = [] self.ipcmd.batch_start() macvlan_prefix = self._get_macvlan_prefix(ifaceobj) - for macvlan_ifacename in glob.glob("/sys/class/net/%s-*" %macvlan_prefix): + 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 @@ -190,8 +242,9 @@ class addressvirtual(moduleBase): 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)) + self.log_error("%s: incorrect address-virtual attrs '%s'" + %(ifaceobj.name, av), ifaceobj, + raise_error=False) av_idx += 1 continue @@ -204,6 +257,18 @@ class addressvirtual(moduleBase): self.ipcmd.batch_commit() self._remove_addresses_from_bridge(ifaceobj, hwaddress) + def check_mac_address(self, ifaceobj, mac): + if mac == 'None': + return True + mac = mac.lower() + try: + if int(mac.split(":")[0], 16) & 1 : + self.logger.error("%s: Multicast bit is set in the virtual mac address '%s'" %(ifaceobj.name, mac)) + return False + return True + except Exception, e: + return False + def _up(self, ifaceobj): address_virtual_list = ifaceobj.get_attr_value('address-virtual') if not address_virtual_list: @@ -212,6 +277,11 @@ class addressvirtual(moduleBase): self._remove_address_config(ifaceobj, address_virtual_list) return + if ifaceobj.upperifaces: + self.log_error('%s: invalid placement of address-virtual lines (must be configured under an interface with no upper interfaces or parent interfaces)' + % (ifaceobj.name), ifaceobj) + return + if not self.ipcmd.link_exists(ifaceobj.name): return self._apply_address_config(ifaceobj, address_virtual_list) @@ -254,6 +324,14 @@ class addressvirtual(moduleBase): av_idx += 1 continue raddrs = raddrs.keys() + try: + av_attrs[0] = ':'.join([i if len(i) == 2 else '0%s' % i + for i in av_attrs[0].split(':')]) + except: + self.logger.info('%s: %s: invalid value for address-virtual (%s)' + % (ifaceobj.name, + macvlan_ifacename, + ' '.join(av_attrs))) 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', @@ -291,7 +369,7 @@ class addressvirtual(moduleBase): def _init_command_handlers(self): if not self.ipcmd: - self.ipcmd = iproute2(**self.get_flags()) + self.ipcmd = iproute2() def run(self, ifaceobj, operation, query_ifaceobj=None, **extra_args): """ run vlan configuration on the interface object passed as argument diff --git a/addons/bond.py b/addons/bond.py new file mode 100644 index 0000000..35fa52f --- /dev/null +++ b/addons/bond.py @@ -0,0 +1,455 @@ +#!/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.bondutil import bondutil +from ifupdownaddons.iproute2 import iproute2 +from ifupdown.netlink import netlink +import ifupdown.policymanager as policymanager +import ifupdown.ifupdownflags as ifupdownflags +from ifupdown.utils import utils + +class bond(moduleBase): + """ ifupdown2 addon module to configure bond interfaces """ + _modinfo = { 'mhelp' : 'bond configuration module', + 'attrs' : { + 'bond-use-carrier': + {'help' : 'bond use carrier', + 'validvals' : ['yes', 'no', '0', '1'], + 'default' : 'yes', + 'example': ['bond-use-carrier yes']}, + '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': ['0', 'balance-rr', + '1', 'active-backup', + '2', 'balance-xor', + '3', 'broadcast', + '4', '802.3ad', + '5', 'balance-tlb', + '6', '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', + 'validrange' : ['0', '255'], + 'example' : ['bond-min-links 0']}, + 'bond-ad-sys-priority': + {'help' : '802.3ad system priority', + 'default' : '65535', + 'validrange' : ['0', '65535'], + 'example' : ['bond-ad-sys-priority 65535'], + 'deprecated' : True, + 'new-attribute' : 'bond-ad-actor-sys-prio'}, + 'bond-ad-actor-sys-prio': + {'help' : '802.3ad system priority', + 'default' : '65535', + 'validrange' : ['0', '65535'], + 'example' : ['bond-ad-actor-sys-prio 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'], + 'deprecated' : True, + 'new-attribute' : 'bond-ad-actor-system'}, + 'bond-ad-actor-system': + {'help' : '802.3ad system mac address', + 'default' : '00:00:00:00:00:00', + 'example' : ['bond-ad-actor-system 00:00:00:00:00:00'],}, + 'bond-lacp-bypass-allow': + {'help' : 'allow lacp bypass', + 'validvals' : ['yes', 'no', '0', '1'], + 'default' : 'no', + 'example' : ['bond-lacp-bypass-allow no']}, + 'bond-slaves' : + {'help' : 'bond slaves', + 'required' : True, + 'multivalue' : True, + 'example' : ['bond-slaves swp1 swp2', + 'bond-slaves glob swp1-2', + 'bond-slaves regex (swp[1|2)']}}} + + _bond_mode_num = {'0': 'balance-rr', + '1': 'active-backup', + '2': 'balance-xor', + '3': 'broadcast', + '4': '802.3ad', + '5': 'balance-tlb', + '6': 'balance-alb'} + + _bond_mode_string = {'balance-rr': '0', + 'active-backup': '1', + 'balance-xor': '2', + 'broadcast': '3', + '802.3ad': '4', + 'balance-tlb': '5', + 'balance-alb': '6'} + + @staticmethod + def _get_readable_bond_mode(mode): + if mode in bond._bond_mode_num: + return bond._bond_mode_num[mode] + return mode + + @staticmethod + def _get_num_bond_mode(mode): + if mode in bond._bond_mode_string: + return bond._bond_mode_string[mode] + return mode + + def __init__(self, *args, **kargs): + ifupdownaddons.modulebase.moduleBase.__init__(self, *args, **kargs) + self.ipcmd = None + self.bondcmd = 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.name, + ifaceobj.get_attr_value_first( + 'bond-slaves'), ifacenames_all) + ifaceobj.dependency_type = ifaceDependencyType.MASTER_SLAVE + # 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 + ifaceobj.link_kind |= ifaceLinkKind.BOND + ifaceobj.role |= ifaceRole.MASTER + + return slave_list + + def get_dependent_ifacenames_running(self, ifaceobj): + self._init_command_handlers() + return self.bondcmd.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(ifaceobj.name, slaves) + else: + return None + + def _is_clag_bond(self, ifaceobj): + if ifaceobj.get_attr_value_first('bond-slaves'): + attrval = ifaceobj.get_attr_value_first('clag-id') + if attrval and attrval != '0': + return True + return False + + def fetch_attr(self, ifaceobj, attrname): + attrval = ifaceobj.get_attr_value_first(attrname) + # grab the defaults from the policy file in case the + # user did not specify something. + policy_default_val = policymanager.policymanager_api.\ + get_iface_default(module_name=self.__class__.__name__, + ifname=ifaceobj.name, + attr=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': + attrval = bond._get_readable_bond_mode(attrval) + if attrval == '802.3ad': + dattrname = 'bond-min-links' + min_links = ifaceobj.get_attr_value_first(dattrname) + if not min_links: + min_links = self.bondcmd.get_min_links(ifaceobj.name) + if min_links == '0': + self.logger.warn('%s: attribute %s' + %(ifaceobj.name, dattrname) + + ' is set to \'0\'') + elif policy_default_val: + return policy_default_val + return attrval + + def _apply_master_settings(self, ifaceobj): + have_attrs_to_set = 0 + linkup = False + bondcmd_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_actor_system'), + ('bond-ad-actor-system' , 'ad_actor_system'), + ('bond-ad-sys-priority' , 'ad_actor_sys_prio'), + ('bond-ad-actor-sys-prio' , 'ad_actor_sys_prio'), + ('bond-lacp-bypass-allow', 'lacp_bypass')]) + 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 bondcmd_attrmap.items(): + v = self.fetch_attr(ifaceobj, k) + if v: + attrstoset[dstk] = v + if not attrstoset: + return + + # support yes/no attrs + utils.support_yesno_attrs(attrstoset, ['use_carrier', 'lacp_bypass']) + + have_attrs_to_set = 1 + self.bondcmd.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 ifupdownflags.flags.PERFMODE: + runningslaves = self.bondcmd.get_slaves(ifaceobj.name); + + clag_bond = self._is_clag_bond(ifaceobj) + + for slave in Set(slaves).difference(Set(runningslaves)): + if (not ifupdownflags.flags.PERFMODE and + not self.ipcmd.link_exists(slave)): + self.log_error('%s: skipping slave %s, does not exist' + %(ifaceobj.name, slave), ifaceobj, + raise_error=False) + continue + link_up = False + if self.ipcmd.is_link_up(slave): + netlink.link_set_updown(slave, "down") + link_up = True + # If clag bond place the slave in a protodown state; clagd + # will protoup it when it is ready + if clag_bond: + try: + netlink.link_set_protodown(slave, "on") + except Exception, e: + self.logger.error('%s: %s' % (ifaceobj.name, str(e))) + self.ipcmd.link_set(slave, 'master', ifaceobj.name) + if link_up or ifaceobj.link_type != ifaceLinkType.LINK_NA: + try: + netlink.link_set_updown(slave, "up") + except Exception, e: + self.logger.debug('%s: %s' % (ifaceobj.name, str(e))) + pass + + if runningslaves: + for s in runningslaves: + if s not in slaves: + self.bondcmd.remove_slave(ifaceobj.name, s) + if clag_bond: + try: + netlink.link_set_protodown(s, "off") + except Exception, e: + self.logger.error('%s: %s' % (ifaceobj.name, str(e))) + + def _up(self, ifaceobj): + try: + if not self.ipcmd.link_exists(ifaceobj.name): + self.bondcmd.create_bond(ifaceobj.name) + self._apply_master_settings(ifaceobj) + self._add_slaves(ifaceobj) + if ifaceobj.addr_method == 'manual': + netlink.link_set_updown(ifaceobj.name, "up") + except Exception, e: + self.log_error(str(e), ifaceobj) + + def _down(self, ifaceobj): + try: + self.bondcmd.delete_bond(ifaceobj.name) + except Exception, e: + self.log_warn(str(e)) + + def _query_check(self, ifaceobj, ifaceobjcurr): + slaves = None + + if not self.bondcmd.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) + + # support yes/no attributes + utils.support_yesno_attrs(runningattrs, ['bond-use-carrier', + 'bond-lacp-bypass-allow'], + ifaceobj=ifaceobj) + + # support for numerical bond-mode + mode = ifaceobj.get_attr_value_first('bond-mode') + if mode in bond._bond_mode_num: + if 'bond-mode' in runningattrs: + runningattrs['bond-mode'] = bond._get_num_bond_mode(runningattrs['bond-mode']) + + 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: + 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.bondcmd.get_mode(bondname), + 'bond-miimon' : + self.bondcmd.get_miimon(bondname), + 'bond-use-carrier' : + self.bondcmd.get_use_carrier(bondname), + 'bond-lacp-rate' : + self.bondcmd.get_lacp_rate(bondname), + 'bond-min-links' : + self.bondcmd.get_min_links(bondname), + 'bond-ad-actor-system' : + self.bondcmd.get_ad_actor_system(bondname), + 'bond-ad-actor-sys-prio' : + self.bondcmd.get_ad_actor_sys_prio(bondname), + 'bond-xmit-hash-policy' : + self.bondcmd.get_xmit_hash_policy(bondname), + 'bond-lacp-bypass-allow' : + self.bondcmd.get_lacp_bypass_allow(bondname), + 'bond-num-unsol-na' : + self.bondcmd.get_num_unsol_na(bondname), + 'bond-num-grat-arp' : + self.bondcmd.get_num_grat_arp(bondname)} + slaves = self.bondcmd.get_slaves(bondname) + if slaves: + bondattrs['bond-slaves'] = slaves + return bondattrs + + def _query_running(self, ifaceobjrunning): + if not self.bondcmd.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): + if not self.ipcmd: + self.ipcmd = iproute2() + if not self.bondcmd: + self.bondcmd = bondutil() + + 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/addons/bridge.py b/addons/bridge.py index 012465a..803051f 100644 --- a/addons/bridge.py +++ b/addons/bridge.py @@ -6,17 +6,21 @@ from sets import Set from ifupdown.iface import * +import ifupdown.policymanager as policymanager +from ifupdown.utils import utils 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 +from ifupdown.netlink import netlink +import ifupdown.ifupdownflags as ifupdownflags import itertools import re import time class bridgeFlags: PORT_PROCESSED = 0x1 + PORT_PROCESSED_OVERRIDE = 0x2 class bridge(moduleBase): """ ifupdown2 addon module to configure linux bridges """ @@ -32,9 +36,11 @@ class bridge(moduleBase): {'help' : 'vlan aware bridge. Setting this ' + 'attribute to yes enables vlan filtering' + ' on the bridge', + 'validvals' : ['yes', 'no'], 'example' : ['bridge-vlan-aware yes/no']}, 'bridge-ports' : {'help' : 'bridge ports', + 'multivalue' : True, 'required' : True, 'example' : ['bridge-ports swp1.100 swp2.100 swp3.100', 'bridge-ports glob swp1-3.100', @@ -46,107 +52,137 @@ class bridge(moduleBase): 'default' : 'no'}, 'bridge-bridgeprio' : {'help': 'bridge priority', + 'validrange' : ['0', '65535'], 'example' : ['bridge-bridgeprio 32768'], 'default' : '32768'}, 'bridge-ageing' : {'help': 'bridge ageing', + 'validrange' : ['0', '65535'], 'example' : ['bridge-ageing 300'], 'default' : '300'}, 'bridge-fd' : { 'help' : 'bridge forward delay', + 'validrange' : ['0', '255'], 'example' : ['bridge-fd 15'], 'default' : '15'}, 'bridge-gcint' : # XXX: recheck values { 'help' : 'bridge garbage collection interval in secs', + 'validrange' : ['0', '255'], 'example' : ['bridge-gcint 4'], - 'default' : '4'}, + 'default' : '4', + 'compat' : True, + 'deprecated': True}, 'bridge-hello' : { 'help' : 'bridge set hello time', + 'validrange' : ['0', '255'], 'example' : ['bridge-hello 2'], 'default' : '2'}, 'bridge-maxage' : { 'help' : 'bridge set maxage', + 'validrange' : ['0', '255'], 'example' : ['bridge-maxage 20'], 'default' : '20'}, 'bridge-pathcosts' : { 'help' : 'bridge set port path costs', - 'example' : ['bridge-pathcosts swp1=100 swp2=100'], + 'validrange' : ['0', '65535'], + 'example' : ['under the bridge: bridge-pathcosts swp1=100 swp2=100', + 'under the port (recommended): bridge-pathcosts 100'], 'default' : '100'}, 'bridge-portprios' : { 'help' : 'bridge port prios', - 'example' : ['bridge-portprios swp1=32 swp2=32'], + 'validrange' : ['0', '65535'], + 'example' : ['under the bridge: bridge-portprios swp1=32 swp2=32', + 'under the port (recommended): bridge-portprios 32'], 'default' : '32'}, 'bridge-mclmc' : { 'help' : 'set multicast last member count', + 'validrange' : ['0', '255'], 'example' : ['bridge-mclmc 2'], 'default' : '2'}, 'bridge-mcrouter' : { 'help' : 'set multicast router', - 'default' : '1', - 'example' : ['bridge-mcrouter 1']}, + 'validvals' : ['yes', 'no', '0', '1'], + 'default' : 'yes', + 'example' : ['bridge-mcrouter yes']}, 'bridge-mcsnoop' : { 'help' : 'set multicast snooping', - 'default' : '1', - 'example' : ['bridge-mcsnoop 1']}, + 'validvals' : ['yes', 'no', '0', '1'], + 'default' : 'yes', + 'example' : ['bridge-mcsnoop yes']}, 'bridge-mcsqc' : { 'help' : 'set multicast startup query count', + 'validrange' : ['0', '255'], 'default' : '2', 'example' : ['bridge-mcsqc 2']}, 'bridge-mcqifaddr' : { 'help' : 'set multicast query to use ifaddr', - 'default' : '0', - 'example' : ['bridge-mcqifaddr 0']}, + 'validvals' : ['yes', 'no', '0', '1'], + 'default' : 'no', + 'example' : ['bridge-mcqifaddr no']}, 'bridge-mcquerier' : { 'help' : 'set multicast querier', - 'default' : '0', - 'example' : ['bridge-mcquerier 0']}, + 'validvals' : ['yes', 'no', '0', '1'], + 'default' : 'no', + 'example' : ['bridge-mcquerier no']}, 'bridge-hashel' : { 'help' : 'set hash elasticity', + 'validrange' : ['0', '4096'], 'default' : '4096', 'example' : ['bridge-hashel 4096']}, 'bridge-hashmax' : { 'help' : 'set hash max', + 'validrange' : ['0', '4096'], 'default' : '4096', 'example' : ['bridge-hashmax 4096']}, 'bridge-mclmi' : { 'help' : 'set multicast last member interval (in secs)', + 'validrange' : ['0', '255'], 'default' : '1', 'example' : ['bridge-mclmi 1']}, 'bridge-mcmi' : { 'help' : 'set multicast membership interval (in secs)', + 'validrange' : ['0', '255'], 'default' : '260', 'example' : ['bridge-mcmi 260']}, 'bridge-mcqpi' : { 'help' : 'set multicast querier interval (in secs)', + 'validrange' : ['0', '255'], 'default' : '255', 'example' : ['bridge-mcqpi 255']}, 'bridge-mcqi' : { 'help' : 'set multicast query interval (in secs)', + 'validrange' : ['0', '255'], 'default' : '125', 'example' : ['bridge-mcqi 125']}, 'bridge-mcqri' : { 'help' : 'set multicast query response interval (in secs)', + 'validrange' : ['0', '255'], 'default' : '10', 'example' : ['bridge-mcqri 10']}, 'bridge-mcsqi' : { 'help' : 'set multicast startup query interval (in secs)', + 'validrange' : ['0', '255'], 'default' : '31', 'example' : ['bridge-mcsqi 31']}, 'bridge-mcqv4src' : { 'help' : 'set per VLAN v4 multicast querier source address', + 'validvals' : ['', ], + 'multivalue' : True, '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']}, + 'validvals' : ['yes', 'no', '0', '1'], + 'default' : 'yes', + 'example' : ['under the bridge: bridge-portmcrouter swp1=yes swp2=yes', + 'under the port (recommended): bridge-portmcrouter yes']}, 'bridge-portmcfl' : { 'help' : 'port multicast fast leave.', + 'validrange' : ['0', '65535'], 'default' : '0', 'example' : ['under the bridge: bridge-portmcfl swp1=0 swp2=0', - 'under the port: bridge-portmcfl 0']}, + 'under the port (recommended): bridge-portmcfl 0']}, 'bridge-waitport' : { 'help' : 'wait for a max of time secs for the' + ' specified ports to become available,' + @@ -160,11 +196,12 @@ class bridge(moduleBase): 'example' : ['bridge-waitport 4 swp1 swp2']}, 'bridge-maxwait' : { 'help' : 'forces to time seconds the maximum time ' + - 'that the Debian bridge setup scripts will ' + + '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', + 'validrange' : ['0', '255'], 'default' : '0', 'example' : ['bridge-maxwait 3']}, 'bridge-vids' : @@ -172,17 +209,27 @@ class bridge(moduleBase): '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', + 'bridge-vids attribute 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', + 'validrange' : ['0', '4096'], 'example' : ['bridge-pvid 1']}, 'bridge-access' : { 'help' : 'bridge port access vlan. Must be ' + 'specified under the bridge port', + 'validrange' : ['0', '4096'], 'example' : ['bridge-access 300']}, + 'bridge-allow-untagged' : + { 'help' : 'indicate if the bridge port accepts ' + + 'untagged packets or not. Must be ' + + 'specified under the bridge port. ' + + 'Default is \'yes\'', + 'validvals' : ['yes', 'no'], + 'example' : ['bridge-allow-untagged yes'], + 'default' : 'yes'}, 'bridge-port-vids' : { 'help' : 'bridge vlans', 'compat': True, @@ -203,12 +250,31 @@ class bridge(moduleBase): 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))) + default_stp_attr = policymanager.policymanager_api.get_attr_default( + module_name=self.__class__.__name__, attr='bridge-stp') + if (default_stp_attr and (default_stp_attr == 'on' or default_stp_attr == 'yes')): + self.default_stp_on = True + else: + self.default_stp_on = False + + should_warn = policymanager.policymanager_api.\ + get_module_globals(module_name=self.__class__.__name__, + attr='warn_on_untagged_bridge_absence') + self.warn_on_untagged_bridge_absence = should_warn == 'yes' + def _is_bridge(self, ifaceobj): if ifaceobj.get_attr_value_first('bridge-ports'): return True return False + def _get_ifaceobj_bridge_ports(self, ifaceobj): + ports = ifaceobj.get_attr_value('bridge-ports') + if ports and len(ports) > 1: + self.log_warn('%s: ignoring duplicate bridge-ports lines: %s' + %(ifaceobj.name, ports[1:])) + return ports[0] if ports else None + def _is_bridge_port(self, ifaceobj): if self.brctlcmd.is_bridge_port(ifaceobj.name): return True @@ -220,10 +286,15 @@ class bridge(moduleBase): if ifaceobj.link_type != ifaceLinkType.LINK_NA: ifaceobj.link_type = ifaceLinkType.LINK_MASTER ifaceobj.link_kind |= ifaceLinkKind.BRIDGE + # for special vlan aware bridges, we need to add another bit + if ifaceobj.get_attr_value_first('bridge-vlan-aware') == 'yes': + ifaceobj.link_kind |= ifaceLinkKind.BRIDGE + ifaceobj.link_privflags |= ifaceLinkPrivFlags.BRIDGE_VLAN_AWARE ifaceobj.role |= ifaceRole.MASTER ifaceobj.dependency_type = ifaceDependencyType.MASTER_SLAVE - return self.parse_port_list(ifaceobj.get_attr_value_first( - 'bridge-ports'), ifacenames_all) + return self.parse_port_list(ifaceobj.name, + self._get_ifaceobj_bridge_ports(ifaceobj), + ifacenames_all) def get_dependent_ifacenames_running(self, ifaceobj): self._init_command_handlers() @@ -239,9 +310,9 @@ class bridge(moduleBase): port_list = ifaceobj.lowerifaces if port_list: return port_list - ports = ifaceobj.get_attr_value_first('bridge-ports') + ports = self._get_ifaceobj_bridge_ports(ifaceobj) if ports: - return self.parse_port_list(ports) + return self.parse_port_list(ifaceobj.name, ports) else: return None @@ -259,7 +330,8 @@ class bridge(moduleBase): return if waitporttime <= 0: return try: - waitportlist = self.parse_port_list(waitportvals[1]) + waitportlist = self.parse_port_list(ifaceobj.name, + waitportvals[1]) except IndexError, e: # ignore error and use all bridge ports waitportlist = portlist @@ -277,14 +349,41 @@ class bridge(moduleBase): self.log_warn('%s: unable to process waitport: %s' %(ifaceobj.name, str(e))) - def _ports_enable_disable_ipv6(self, ports, enable='1'): + def _enable_disable_ipv6(self, port, enable='1'): + try: + self.write_file('/proc/sys/net/ipv6/conf/%s/disable_ipv6' % port, enable) + except Exception, e: + self.logger.info(str(e)) + + def handle_ipv6(self, ports, state, ifaceobj=None): + if ifaceobj and (ifaceobj.link_privflags & ifaceLinkPrivFlags.BRIDGE_VXLAN): + self._enable_disable_ipv6(ifaceobj.name, state) for p in ports: + self._enable_disable_ipv6(p, state) + + def _pretty_print_add_ports_error(self, errstr, bridgeifaceobj, bridgeports): + """ pretty print bridge port add errors. + since the commands are batched and the kernel only returns error + codes, this function tries to interpret some error codes + and prints clearer errors """ + + if re.search('RTNETLINK answers: Invalid argument', errstr): + # Cumulus Linux specific error checks try: - self.write_file('/proc/sys/net/ipv6/conf/%s' %p + - '/disable_ipv6', enable) - except Exception, e: - self.logger.info(str(e)) + if self.sysctl_get('net.bridge.bridge-allow-multiple-vlans') == '0': + vlanid = None + for bport in bridgeports: + ifattrs = bport.split('.') + if vlanid: + if (len(ifattrs) == 1 or ifattrs[1] != vlanid): + self.log_error('%s: ' %bridgeifaceobj.name + + 'net.bridge.bridge-allow-multiple-vlans not set, multiple vlans not allowed', bridgeifaceobj) + break + if len(ifattrs) == 2: + vlanid = ifattrs[1] + except: pass + self.log_error(bridgeifaceobj.name + ': ' + errstr, bridgeifaceobj) def _add_ports(self, ifaceobj): bridgeports = self._get_bridge_port_list(ifaceobj) @@ -295,7 +394,7 @@ class bridge(moduleBase): self._process_bridge_waitport(ifaceobj, bridgeports) self.ipcmd.batch_start() # Delete active ports not in the new port list - if not self.PERFMODE: + if not ifupdownflags.flags.PERFMODE: runningbridgeports = self.brctlcmd.get_bridge_ports(ifaceobj.name) if runningbridgeports: for bport in runningbridgeports: @@ -308,11 +407,14 @@ class bridge(moduleBase): self.ipcmd.batch_commit() return err = 0 - for bridgeport in Set(bridgeports).difference(Set(runningbridgeports)): + ports = 0 + newbridgeports = Set(bridgeports).difference(Set(runningbridgeports)) + for bridgeport in newbridgeports: 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)) + if (not ifupdownflags.flags.DRYRUN and + not self.ipcmd.link_exists(bridgeport)): + self.log_error('%s: bridge port %s does not exist' + %(ifaceobj.name, bridgeport), ifaceobj) err += 1 continue hwaddress = self.ipcmd.link_get_hwaddress(bridgeport) @@ -323,17 +425,23 @@ class bridge(moduleBase): continue self.ipcmd.link_set(bridgeport, 'master', ifaceobj.name) self.ipcmd.addr_flush(bridgeport) + ports += 1 + if ports == 250: + ports = 0 + self.ipcmd.batch_commit() + self.ipcmd.batch_start() except Exception, e: self.logger.error(str(e)) pass try: self.ipcmd.batch_commit() except Exception, e: - self.logger.error(str(e)) + self._pretty_print_add_ports_error(str(e), ifaceobj, + bridgeports) pass # enable ipv6 for ports that were removed - self._ports_enable_disable_ipv6(removedbridgeports, '0') + self.handle_ipv6(removedbridgeports, '0') if err: self.log_error('bridge configuration failed (missing ports)') @@ -399,12 +507,15 @@ class bridge(moduleBase): for start, end in self._ints_to_ranges(vids2_diff)] return (vids_to_del, vids_to_add) - def _compare_vids(self, vids1, vids2): + def _compare_vids(self, vids1, vids2, pvid=None): """ 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): + set_diff = Set(vids1_ints).symmetric_difference(vids2_ints) + if pvid: + set_diff = set_diff.remove(pvid) + if set_diff: return False else: return True @@ -416,7 +527,7 @@ class bridge(moduleBase): attrval = ifaceobj.get_attr_value_first('bridge-mcqv4src') if attrval: running_mcqv4src = {} - if not self.PERFMODE: + if not ifupdownflags.flags.PERFMODE: running_mcqv4src = self.brctlcmd.get_mcqv4src(ifaceobj.name) mcqs = {} srclist = attrval.split() @@ -434,8 +545,10 @@ class bridge(moduleBase): 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() + + # CM-8161. Removed check for PERFMODE. Need the get in all cases + # including reboot, so that we can configure the pvid correctly. + self._running_vidinfo = self.ipcmd.bridge_port_vids_get_all() self._running_vidinfo_valid = True return self._running_vidinfo @@ -448,17 +561,21 @@ class bridge(moduleBase): # Supports old style vlan vid info format # for compatibility # + bridge_port_pvids = ifaceobj.get_attr_value_first('bridge-port-pvids') + bridge_port_vids = ifaceobj.get_attr_value_first('bridge-port-vids') + if not bridge_port_pvids and not bridge_port_vids: + return # 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 bridge_port_pvids: + portlist = self.parse_port_list(ifaceobj.name, bridge_port_pvids) if not portlist: self.log_warn('%s: could not parse \'%s %s\'' - %(ifaceobj.name, attrname, attrval)) + %(ifaceobj.name, 'bridge-port-pvids', + bridge_port_pvids)) return for p in portlist: try: @@ -475,12 +592,11 @@ class bridge(moduleBase): %(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 bridge_port_vids: + portlist = self.parse_port_list(ifaceobj.name, bridge_port_vids) if not portlist: - self.log_warn('%s: could not parse \'%s %s\'' - %(ifaceobj.name, attrname, attrval)) + self.log_warn('%s: could not parse \'%s %s\'' %(ifaceobj.name, + 'bridge-port-vids', bridge_port_vids)) return for p in portlist: try: @@ -521,21 +637,43 @@ class bridge(moduleBase): # if running_vids: # self.ipcmd.bridge_vids_del(ifaceobj.name, running_vids) + + def _is_running_stp_state_on(self, bridgename): + """ Returns True if running stp state is on, else False """ + + stp_state_file = '/sys/class/net/%s/bridge/stp_state' %bridgename + if not stp_state_file: + return False + running_stp_state = self.read_file_oneline(stp_state_file) + if running_stp_state and running_stp_state != '0': + return True + return False + + def _is_config_stp_state_on(self, ifaceobj): + """ Returns true if user specified stp state is on, else False """ + + stp_attr = ifaceobj.get_attr_value_first('bridge-stp') + if not stp_attr: + return self.default_stp_on + if (stp_attr and (stp_attr == 'on' or stp_attr == 'yes')): + return True + return False + def _apply_bridge_settings(self, ifaceobj): try: - stp = ifaceobj.get_attr_value_first('bridge-stp') - if stp: - self.brctlcmd.set_stp(ifaceobj.name, stp) + if self._is_config_stp_state_on(ifaceobj): + if not self._is_running_stp_state_on(ifaceobj.name): + self.brctlcmd.set_stp(ifaceobj.name, "on") + self.logger.info('%s: stp state reset, reapplying port ' + 'settings' %ifaceobj.name) + ifaceobj.module_flags[ifaceobj.name] = \ + ifaceobj.module_flags.setdefault(self.name,0) | \ + bridgeFlags.PORT_PROCESSED_OVERRIDE 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': + if self._is_running_stp_state_on(ifaceobj.name): 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 @@ -546,8 +684,6 @@ class bridge(moduleBase): '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' : @@ -586,6 +722,10 @@ class bridge(moduleBase): }.items() if v } if bridgeattrs: + utils.support_yesno_attrs(bridgeattrs, ['mcqifaddr', + 'mcquerier', + 'mcrouter', + 'mcsnoop']) self.brctlcmd.set_bridge_attrs(ifaceobj.name, bridgeattrs) portattrs = {} for attrname, dstattrname in {'bridge-pathcosts' : 'pathcost', @@ -595,33 +735,39 @@ class bridge(moduleBase): attrval = ifaceobj.get_attr_value_first(attrname) if not attrval: continue - portlist = self.parse_port_list(attrval) + portlist = self.parse_port_list(ifaceobj.name, attrval) if not portlist: - self.log_warn('%s: could not parse \'%s %s\'' - %(ifaceobj.name, attrname, attrval)) + self.log_error('%s: could not parse \'%s %s\'' + %(ifaceobj.name, attrname, attrval), ifaceobj, + raise_error=False) continue for p in portlist: try: (port, val) = p.split('=') if not portattrs.get(port): portattrs[port] = {} - portattrs[port].update({dstattrname : val}) + if attrname == 'bridge-portmcrouter': + portattrs[port].update({dstattrname: utils.boolean_support_binary(val)}) + else: + portattrs[port].update({dstattrname : val}) except Exception, e: - self.log_warn('%s: could not parse %s (%s)' - %(ifaceobj.name, attrname, str(e))) + self.log_error('%s: could not parse %s (%s)' + %(ifaceobj.name, attrname, str(e)), + ifaceobj, raise_error=False) 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)) + self.log_error('%s: %s' %(ifaceobj.name, str(e)), ifaceobj, + raise_error=False) 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)) + self.log_error(str(e), ifaceobj) def _check_vids(self, ifaceobj, vids): ret = True @@ -654,8 +800,9 @@ class bridge(moduleBase): 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))) + self.log_error('%s: failed to set vid `%s` (%s)' + %(bportifaceobj.name, str(vids), str(e)), + bportifaceobj) def _apply_bridge_port_pvids(self, bportifaceobj, pvid, running_pvid): # Install pvids @@ -668,8 +815,8 @@ class bridge(moduleBase): 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))) + self.log_error('%s: failed to set pvid `%s` (%s)' + %(bportifaceobj.name, pvid, str(e)), bportifaceobj) def _apply_bridge_vids_and_pvid(self, bportifaceobj, vids, running_vids, pvid, running_pvid, isbridge): @@ -687,14 +834,14 @@ class bridge(moduleBase): vids_to_del = [] vids_to_add = vids pvid_to_del = None - pvid_to_add = pvid if pvid else '1' + pvid_to_add = pvid if running_vids: (vids_to_del, vids_to_add) = \ self._diff_vids(vids, running_vids) if running_pvid: - if running_pvid != pvid: + if running_pvid != pvid and running_pvid != '0': pvid_to_del = running_pvid if (pvid_to_del and (pvid_to_del in vids) and @@ -714,9 +861,10 @@ class bridge(moduleBase): # vid 100 102 vids_to_add.append(pvid_to_del) except Exception, e: - self.log_warn('%s: failed to process vids/pvids' - %bportifaceobj.name + ' vids = %s' %str(vids) + - 'pvid = %s ' %pvid + '(%s)' %str(e)) + self.log_error('%s: failed to process vids/pvids' + %bportifaceobj.name + ' vids = %s' %str(vids) + + 'pvid = %s ' %pvid + '(%s)' %str(e), + bportifaceobj, raise_error=False) try: if vids_to_del: self.ipcmd.bridge_vids_del(bportifaceobj.name, @@ -738,14 +886,17 @@ class bridge(moduleBase): self.ipcmd.bridge_vids_add(bportifaceobj.name, vids_to_add, isbridge) except Exception, e: - self.log_warn('%s: failed to set vid `%s` (%s)' - %(bportifaceobj.name, str(vids_to_add), str(e))) + self.log_error('%s: failed to set vid `%s` (%s)' + %(bportifaceobj.name, str(vids_to_add), str(e)), + bportifaceobj, raise_error=False) try: - self.ipcmd.bridge_port_pvid_add(bportifaceobj.name, pvid_to_add) + if pvid_to_add: + self.ipcmd.bridge_port_pvid_add(bportifaceobj.name, pvid_to_add) except Exception, e: - self.log_warn('%s: failed to set pvid `%s` (%s)' - %(bportifaceobj.name, pvid_to_add, str(e))) + self.log_error('%s: failed to set pvid `%s` (%s)' + %(bportifaceobj.name, pvid_to_add, str(e)), + bportifaceobj) def _apply_bridge_vlan_aware_port_settings_all(self, bportifaceobj, bridge_vids=None, @@ -759,7 +910,10 @@ class bridge(moduleBase): if bport_access: vids = re.split(r'[\s\t]\s*', bport_access) pvids = vids + allow_untagged = 'yes' else: + allow_untagged = bportifaceobj.get_attr_value_first('bridge-allow-untagged') or 'yes' + bport_vids = bportifaceobj.get_attr_value_first('bridge-vids') if bport_vids: vids = re.split(r'[\s\t,]\s*', bport_vids) @@ -773,10 +927,15 @@ class bridge(moduleBase): elif bridge_vids: vids_final = bridge_vids - if pvids: - pvid_final = pvids[0] - elif bridge_pvid: - pvid_final = bridge_pvid + if allow_untagged == 'yes': + if pvids: + pvid_final = pvids[0] + elif bridge_pvid: + pvid_final = bridge_pvid + else: + pvid_final = '1' + else: + pvid_final = None self._apply_bridge_vids_and_pvid(bportifaceobj, vids_final, running_vidinfo.get(bportifaceobj.name, {}).get('vlan'), @@ -809,17 +968,12 @@ class bridge(moduleBase): self.brctlcmd.set_bridgeport_attrs(bridgename, bportifaceobj.name, portattrs) except Exception, e: - self.log_warn(str(e)) + self.log_error(str(e), bportifaceobj) 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 + bridge_vlan_aware = ifaceobj.get_attr_value_first('bridge-vlan-aware') == 'yes' if (ifaceobj.get_attr_value_first('bridge-port-vids') and ifaceobj.get_attr_value_first('bridge-port-pvids')): @@ -841,6 +995,12 @@ class bridge(moduleBase): else: bridge_pvid = None + if (ifaceobj.module_flags.get(self.name, 0x0) & + bridgeFlags.PORT_PROCESSED_OVERRIDE): + port_processed_override = True + else: + port_processed_override = False + bridgeports = self._get_bridge_port_list(ifaceobj) if not bridgeports: self.logger.debug('%s: cannot find bridgeports' %ifaceobj.name) @@ -859,8 +1019,10 @@ class bridge(moduleBase): continue for bportifaceobj in bportifaceobjlist: # Dont process bridge port if it already has been processed - if (bportifaceobj.module_flags.get(self.name,0x0) & \ - bridgeFlags.PORT_PROCESSED): + # and there is no override on port_processed + if (not port_processed_override and + (bportifaceobj.module_flags.get(self.name,0x0) & + bridgeFlags.PORT_PROCESSED)): continue try: # Add attributes specific to the vlan aware bridge @@ -869,6 +1031,8 @@ class bridge(moduleBase): bportifaceobj, bridge_vids, bridge_pvid) self._apply_bridge_port_settings(bportifaceobj, bridgeifaceobj=ifaceobj) + elif self.warn_on_untagged_bridge_absence: + self._check_untagged_bridge(ifaceobj.name, bportifaceobj, ifaceobj_getfunc) except Exception, e: err = True self.logger.warn('%s: %s' %(ifaceobj.name, str(e))) @@ -876,35 +1040,74 @@ class bridge(moduleBase): if err: raise Exception('%s: errors applying port settings' %ifaceobj.name) + def _check_untagged_bridge(self, bridgename, bridgeportifaceobj, ifaceobj_getfunc): + if bridgeportifaceobj.link_kind & ifaceLinkKind.VLAN: + lower_ifaceobj_list = ifaceobj_getfunc(bridgeportifaceobj.lowerifaces[0]) + if lower_ifaceobj_list and lower_ifaceobj_list[0] and \ + not lower_ifaceobj_list[0].link_privflags & ifaceLinkPrivFlags.BRIDGE_PORT: + self.logger.warn('%s: untagged bridge not found. Please configure a bridge with untagged bridge ports to avoid Spanning Tree Interoperability issue.' % bridgename) + self.warn_on_untagged_bridge_absence = False + + def _get_bridgename(self, ifaceobj): + for u in ifaceobj.upperifaces: + if self.ipcmd.is_bridge(u): + return u + return None + def _up(self, ifaceobj, ifaceobj_getfunc=None): - # Check if bridge port + # Check if bridge port and see if we need to add it to the bridge + add_port = False bridgename = self.ipcmd.bridge_port_get_bridge_name(ifaceobj.name) + if (not bridgename and + (ifaceobj.link_privflags & ifaceLinkPrivFlags.BRIDGE_PORT)): + # get bridgename and add port to bridge + bridgename = self._get_bridgename(ifaceobj) + add_port = True 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.module_flags[self.name] = ifaceobj.module_flags.setdefault(self.name,0) | \ + if self.ipcmd.bridge_is_vlan_aware(bridgename): + if add_port: + # add ifaceobj to bridge + self.ipcmd.link_set(ifaceobj.name, 'master', 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.module_flags[self.name] = ifaceobj.module_flags.setdefault(self.name,0) | \ bridgeFlags.PORT_PROCESSED - return + return if not self._is_bridge(ifaceobj): return err = False errstr = '' running_ports = '' + bridge_just_created = False try: - if not self.PERFMODE: + if not ifupdownflags.flags.PERFMODE: if not self.ipcmd.link_exists(ifaceobj.name): self.ipcmd.link_create(ifaceobj.name, 'bridge') + bridge_just_created = True else: self.ipcmd.link_create(ifaceobj.name, 'bridge') + bridge_just_created = True except Exception, e: raise Exception(str(e)) + + try: + if ifaceobj.get_attr_value_first('bridge-vlan-aware') == 'yes': + if (bridge_just_created or + not self.ipcmd.bridge_is_vlan_aware(ifaceobj.name)): + self.ipcmd.link_set(ifaceobj.name, 'vlan_filtering', '1', + False, "bridge") + if not bridge_just_created: + ifaceobj.module_flags[self.name] = ifaceobj.module_flags.setdefault(self.name,0) | bridgeFlags.PORT_PROCESSED_OVERRIDE + + except Exception, e: + raise Exception(str(e)) + try: self._add_ports(ifaceobj) except Exception, e: @@ -924,7 +1127,7 @@ class bridge(moduleBase): if not running_ports: return # disable ipv6 for ports that were added to bridge - self._ports_enable_disable_ipv6(running_ports, '1') + self.handle_ipv6(running_ports, '1', ifaceobj=ifaceobj) self._apply_bridge_port_settings_all(ifaceobj, ifaceobj_getfunc=ifaceobj_getfunc) except Exception, e: @@ -936,27 +1139,27 @@ class bridge(moduleBase): if ifaceobj.link_type != ifaceLinkType.LINK_NA: for p in running_ports: try: - rtnetlink_api.rtnl_api.link_set(p, "up") + netlink.link_set_updown(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") + netlink.link_set_updown(ifaceobj.name, "up") if err: raise Exception(errstr) def _down(self, ifaceobj, ifaceobj_getfunc=None): try: - if ifaceobj.get_attr_value_first('bridge-ports'): + if self._get_ifaceobj_bridge_ports(ifaceobj): ports = self.brctlcmd.get_bridge_ports(ifaceobj.name) self.brctlcmd.delete_bridge(ifaceobj.name) if ports: - self._ports_enable_disable_ipv6(ports, '0') + self.handle_ipv6(ports, '0') if ifaceobj.link_type != ifaceLinkType.LINK_NA: - map(lambda p: rtnetlink_api.rtnl_api.link_set(p, - "down"), ports) + map(lambda p: netlink.link_set_updown(p, "down"), + ports) except Exception, e: self.log_error(str(e)) @@ -1009,7 +1212,7 @@ class bridge(moduleBase): running_bridgeport_vids.append(' '.join(vids)) pvids = running_vidinfo.get(bport, {}).get('pvid') if pvids: - running_bridgeport_pvids.append(pvids[0]) + running_bridgeport_pvids.append(pvids) bridge_vids = None if running_bridgeport_vids: @@ -1155,10 +1358,10 @@ class bridge(moduleBase): attrval = ifaceobj.get_attr_value_first('bridge-port-vids') if attrval: running_bridge_port_vids = '' - portlist = self.parse_port_list(attrval) + portlist = self.parse_port_list(ifaceobj.name, attrval) if not portlist: - self.log_warn('%s: could not parse \'%s %s\'' - %(ifaceobj.name, attrname, attrval)) + self.log_warn('%s: could not parse \'bridge-port-vids %s\'' + %(ifaceobj.name, attrval)) return err = 0 for p in portlist: @@ -1187,10 +1390,10 @@ class bridge(moduleBase): attrval = ifaceobj.get_attr_value_first('bridge-port-pvids') if attrval: - portlist = self.parse_port_list(attrval) + portlist = self.parse_port_list(ifaceobj.name, attrval) if not portlist: - self.log_warn('%s: could not parse \'%s %s\'' - %(ifaceobj.name, attrname, attrval)) + self.log_warn('%s: could not parse \'bridge-port-pvids %s\'' + %(ifaceobj.name, attrval)) return running_bridge_port_pvids = '' err = 0 @@ -1245,6 +1448,9 @@ class bridge(moduleBase): ifaceattrs = self.dict_key_subset(ifaceobj.config, self.get_mod_attrs()) + #Add default attributes if --with-defaults is set + if ifupdownflags.flags.WITHDEFAULTS and 'bridge-stp' not in ifaceattrs: + ifaceattrs.append('bridge-stp') if not ifaceattrs: return try: @@ -1256,18 +1462,28 @@ class bridge(moduleBase): except Exception, e: self.logger.warn(str(e)) runningattrs = {} + + self._query_check_support_yesno_attrs(runningattrs, ifaceobj) + 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 + if ifupdownflags.flags.WITHDEFAULTS and k == 'bridge-stp': + v = 'on' if self.default_stp_on else 'off' + else: + 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): + if k == 'bridge-maxwait' or k == 'bridge-waitport': + ifaceobjcurr.update_config_with_status(k, v, 0) + continue + if k == 'bridge-vlan-aware': + rv = self.ipcmd.bridge_is_vlan_aware(ifaceobj.name) + if (rv and v == 'yes') or (not rv and v == 'no'): ifaceobjcurr.update_config_with_status('bridge-vlan-aware', v, 0) else: @@ -1277,14 +1493,14 @@ class bridge(moduleBase): # special case stp compare because it may # contain more than one valid values stp_on_vals = ['on', 'yes'] - stp_off_vals = ['off'] + stp_off_vals = ['off', 'no'] 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) + rv, 0) else: ifaceobjcurr.update_config_with_status('bridge-stp', - v, 1) + rv, 1) elif k == 'bridge-ports': # special case ports because it can contain regex or glob running_port_list = rv.keys() if rv else [] @@ -1303,12 +1519,12 @@ class bridge(moduleBase): elif (k == 'bridge-pathcosts' or k == 'bridge-portprios' or k == 'bridge-portmcrouter' or k == 'bridge-portmcfl'): - brctlcmdattrname = k[11:].rstrip('s') + brctlcmdattrname = k[7:].rstrip('s') # for port attributes, the attributes are in a list # = status = 0 currstr = '' - vlist = self.parse_port_list(v) + vlist = self.parse_port_list(ifaceobj.name, v) if not vlist: continue for vlistitem in vlist: @@ -1328,7 +1544,19 @@ class bridge(moduleBase): pass ifaceobjcurr.update_config_with_status(k, currstr, status) elif not rv: - ifaceobjcurr.update_config_with_status(k, 'notfound', 1) + if k == 'bridge-pvid' or k == 'bridge-vids' or k == 'bridge-allow-untagged': + # bridge-pvid and bridge-vids on a bridge does + # not correspond directly to a running config + # on the bridge. They correspond to default + # values for the bridge ports. And they are + # already checked against running config of the + # bridge port and reported against a bridge port. + # So, ignore these attributes under the bridge. + # Use '2' for ignore today. XXX: '2' will be + # mapped to a defined value in subsequent patches. + ifaceobjcurr.update_config_with_status(k, v, 2) + else: + ifaceobjcurr.update_config_with_status(k, 'notfound', 1) continue elif v != rv: ifaceobjcurr.update_config_with_status(k, rv, 1) @@ -1351,6 +1579,8 @@ class bridge(moduleBase): pvid = None for ifaceobj in ifaceobjs: pvid = ifaceobj.get_attr_value_first('bridge-pvid') + if pvid: + break return pvid def _get_bridge_name(self, ifaceobj): @@ -1375,6 +1605,21 @@ class bridge(moduleBase): ifaceobjcurr.update_config_with_status(attr_name, vids, 0) return + running_pvid = running_vidinfo.get(ifaceobj.name, + {}).get('pvid') + attr_name = 'bridge-pvid' + pvid = self._get_bridge_pvid(bridgename, ifaceobj_getfunc) + 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' + attr_name = 'bridge-vids' vids = ifaceobj.get_attr_value_first(attr_name) if vids: @@ -1393,25 +1638,10 @@ class bridge(moduleBase): 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))): + not self._compare_vids(bridge_vids, running_vids, pvid))): 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): @@ -1443,6 +1673,12 @@ class bridge(moduleBase): try: running_attrval = self.brctlcmd.get_bridgeport_attr( bridgename, ifaceobj.name, dstattr) + + if dstattr == 'mcrouter': + if not utils.is_binary_bool(attrval) and running_attrval: + running_attrval = utils.get_yesno_boolean( + utils.get_boolean_from_string(running_attrval)) + if running_attrval != attrval: ifaceobjcurr.update_config_with_status(attr, running_attrval, 1) @@ -1528,21 +1764,52 @@ class bridge(moduleBase): elif self.brctlcmd.is_bridge_port(ifaceobjrunning.name): self._query_running_bridge_port(ifaceobjrunning, ifaceobj_getfunc) + def _query(self, ifaceobj, **kwargs): + """ add default policy attributes supported by the module """ + if (not (ifaceobj.link_kind & ifaceLinkKind.BRIDGE) or + ifaceobj.get_attr_value_first('bridge-stp')): + return + if self.default_stp_on: + ifaceobj.update_config('bridge-stp', 'yes') + + def _query_check_support_yesno_attrs(self, runningattrs, ifaceobj): + for attrl in [['mcqifaddr', 'bridge-mcqifaddr'], + ['mcquerier', 'bridge-mcquerier'], + ['mcrouter', 'bridge-mcrouter'], + ['mcsnoop', 'bridge-mcsnoop']]: + value = ifaceobj.get_attr_value_first(attrl[1]) + if value and not utils.is_binary_bool(value): + if attrl[0] in runningattrs: + bool = utils.get_boolean_from_string(runningattrs[attrl[0]]) + runningattrs[attrl[0]] = utils.get_yesno_boolean(bool) + attrval = ifaceobj.get_attr_value_first('bridge-portmcrouter') + if attrval: + portlist = self.parse_port_list(ifaceobj.name, attrval) + if portlist: + to_convert = [] + for p in portlist: + (port, val) = p.split('=') + if not utils.is_binary_bool(val): + to_convert.append(port) + for port in to_convert: + runningattrs['ports'][port]['portmcrouter'] = utils.get_yesno_boolean( + utils.get_boolean_from_string(runningattrs['ports'][port]['portmcrouter'])) + _run_ops = {'pre-up' : _up, 'post-down' : _down, 'query-checkcurr' : _query_check, - 'query-running' : _query_running} + 'query-running' : _query_running, + 'query' : _query} 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) + self.ipcmd = iproute2() if not self.brctlcmd: - self.brctlcmd = brctl(**flags) + self.brctlcmd = brctl() def run(self, ifaceobj, operation, query_ifaceobj=None, ifaceobj_getfunc=None): diff --git a/addons/bridgevlan.py b/addons/bridgevlan.py index 44824c7..80245cc 100644 --- a/addons/bridgevlan.py +++ b/addons/bridgevlan.py @@ -8,6 +8,8 @@ from ifupdown.iface import * from ifupdownaddons.modulebase import moduleBase from ifupdownaddons.iproute2 import iproute2 from ifupdownaddons.bridgeutils import brctl +from ipaddr import IPv4Address +import ifupdown.ifupdownflags as ifupdownflags import logging class bridgevlan(moduleBase): @@ -23,6 +25,7 @@ class bridgevlan(moduleBase): 'bridge-igmp-querier-src' : { 'help' : 'bridge igmp querier src. Must be ' + 'specified under the vlan interface', + 'validvals' : [IPv4Address, ], 'example' : ['bridge-igmp-querier-src 172.16.101.1']}}} def __init__(self, *args, **kargs): @@ -57,8 +60,8 @@ class bridgevlan(moduleBase): (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)') + self.log_error('%s: bridge vlan interface name ' %ifaceobj.name + + 'does not correspond to format (eg. br0.100)', ifaceobj) raise if not self.ipcmd.link_exists(bridgename): @@ -67,7 +70,7 @@ class bridgevlan(moduleBase): return running_mcqv4src = {} - if not self.PERFMODE: + if not ifupdownflags.flags.PERFMODE: running_mcqv4src = self.brctlcmd.get_mcqv4src(bridgename) if running_mcqv4src: r_mcqv4src = running_mcqv4src.get(vlan) @@ -137,9 +140,9 @@ class bridgevlan(moduleBase): def _init_command_handlers(self): if not self.ipcmd: - self.ipcmd = iproute2(**self.get_flags()) + self.ipcmd = iproute2() if not self.brctlcmd: - self.brctlcmd = brctl(**self.get_flags()) + self.brctlcmd = brctl() def run(self, ifaceobj, operation, query_ifaceobj=None, **extra_args): """ run vlan configuration on the interface object passed as argument diff --git a/addons/dhcp.py b/addons/dhcp.py index ed68466..62dbe78 100644 --- a/addons/dhcp.py +++ b/addons/dhcp.py @@ -8,9 +8,11 @@ try: from ipaddr import IPNetwork from sets import Set from ifupdown.iface import * + import ifupdown.policymanager as policymanager from ifupdownaddons.modulebase import moduleBase from ifupdownaddons.dhclient import dhclient from ifupdownaddons.iproute2 import iproute2 + import ifupdown.ifupdownflags as ifupdownflags except ImportError, e: raise ImportError (str(e) + "- required module not found") @@ -23,15 +25,31 @@ class dhcp(moduleBase): self.ipcmd = None def _up(self, ifaceobj): + # if dhclient is already running do not stop and start it + if self.dhclientcmd.is_running(ifaceobj.name) or \ + self.dhclientcmd.is_running6(ifaceobj.name): + self.logger.info('dhclient already running on %s. Not restarting.' % \ + ifaceobj.name) + return try: + dhclient_cmd_prefix = None + dhcp_wait = policymanager.policymanager_api.get_attr_default( + module_name=self.__class__.__name__, attr='dhcp-wait') + wait = not str(dhcp_wait).lower() == "no" + vrf = ifaceobj.get_attr_value_first('vrf') + if (vrf and self.vrf_exec_cmd_prefix and + self.ipcmd.link_exists(vrf)): + dhclient_cmd_prefix = '%s %s' %(self.vrf_exec_cmd_prefix, vrf) + if ifaceobj.addr_family == 'inet': # First release any existing dhclient processes try: - if not self.PERFMODE: + if not ifupdownflags.flags.PERFMODE: self.dhclientcmd.stop(ifaceobj.name) except: pass - self.dhclientcmd.start(ifaceobj.name) + self.dhclientcmd.start(ifaceobj.name, wait=wait, + cmd_prefix=dhclient_cmd_prefix) elif ifaceobj.addr_family == 'inet6': accept_ra = ifaceobj.get_attr_value_first('accept_ra') if accept_ra: @@ -47,12 +65,18 @@ class dhcp(moduleBase): self.dhclientcmd.stop6(ifaceobj.name) except: pass - self.dhclientcmd.start6(ifaceobj.name) + self.dhclientcmd.start6(ifaceobj.name, wait=wait, + cmd_prefix=dhclient_cmd_prefix) except Exception, e: - self.log_error(str(e)) + self.log_error(str(e), ifaceobj) def _down(self, ifaceobj): - self.dhclientcmd.release(ifaceobj.name) + dhclient_cmd_prefix = None + vrf = ifaceobj.get_attr_value_first('vrf') + if (vrf and self.vrf_exec_cmd_prefix and + self.ipcmd.link_exists(vrf)): + dhclient_cmd_prefix = '%s %s' %(self.vrf_exec_cmd_prefix, vrf) + self.dhclientcmd.release(ifaceobj.name, dhclient_cmd_prefix) self.ipcmd.link_down(ifaceobj.name) def _query_check(self, ifaceobj, ifaceobjcurr): @@ -93,7 +117,7 @@ class dhcp(moduleBase): def _init_command_handlers(self): if not self.ipcmd: - self.ipcmd = iproute2(**self.get_flags()) + self.ipcmd = iproute2() def run(self, ifaceobj, operation, query_ifaceobj=None, **extra_args): """ run dhcp configuration on the interface object passed as argument diff --git a/addons/ethtool.py b/addons/ethtool.py index f578128..948c91e 100644 --- a/addons/ethtool.py +++ b/addons/ethtool.py @@ -10,9 +10,11 @@ try: from ipaddr import IPNetwork from sets import Set from ifupdown.iface import * + from ifupdown.utils import utils from ifupdownaddons.utilsbase import * from ifupdownaddons.modulebase import moduleBase from ifupdownaddons.iproute2 import iproute2 + import ifupdown.ifupdownflags as ifupdownflags except ImportError, e: raise ImportError (str(e) + "- required module not found") @@ -23,6 +25,7 @@ class ethtool(moduleBase,utilsBase): 'attrs': { 'link-speed' : {'help' : 'set link speed', + 'validvals' : ['100', '1000', '10000', '40000', '100000'], 'example' : ['link-speed 1000'], 'default' : 'varies by platform and port'}, 'link-duplex' : @@ -33,12 +36,14 @@ class ethtool(moduleBase,utilsBase): 'link-autoneg' : {'help': 'set autonegotiation', 'example' : ['link-autoneg on'], - 'validvals' : ['on', 'off'], + 'validvals' : ['yes', 'no', 'on', 'off'], 'default' : 'varies by platform and port'}}} def __init__(self, *args, **kargs): moduleBase.__init__(self, *args, **kargs) self.ipcmd = None + # keep a list of iface objects who have modified link attributes + self.ifaceobjs_modified_configs = [] def _post_up(self, ifaceobj, operation='post_up'): """ @@ -56,21 +61,46 @@ class ethtool(moduleBase,utilsBase): ifname=ifaceobj.name, attr='link-%s'%attr) + if not default_val and not config_val: + # there is no point in checking the running config + # if we have no default and the user did not have settings + continue # check running values - running_val = None + running_val = self.get_running_attr(attr, ifaceobj) + if attr == 'autoneg': - # we can only get autoneg from ethtool - output = self.exec_commandl(['ethtool', ifaceobj.name]) - running_val = self.get_autoneg(ethtool_output=output) - else: - running_val = self.read_file_oneline('/sys/class/net/%s/%s' % \ - (ifaceobj.name, attr)) + config_val = utils.get_onoff_bool(config_val) + + # we need to track if an interface has a configured value + # this will be used if there are duplicate iface stanza and + # the configured interface will always take precedence. + # so even if we do not change the settings because they match + # what is configured, we need to append it here so that later duplicate + # ifaces will see that we had a configured iface and not change things. if config_val and config_val == running_val: # running value is what is configured, do nothing + # this prevents unconfigured ifaces from resetting to default + self.ifaceobjs_modified_configs.append(ifaceobj.name) continue + if not config_val and default_val and default_val == running_val: # nothing configured but the default is running continue + # if we are the oldest sibling, we have to reset to defaults + # unless a previous sibling had link attr configured and made changes + if ((ifaceobj.flags & iface.HAS_SIBLINGS) and + (ifaceobj.flags & iface.OLDEST_SIBLING) and + (ifaceobj.name in self.ifaceobjs_modified_configs)): + continue + + # if we are not the oldest and we have no configs, do not change anything + # the only way a non-oldest sibling would change values is if it + # had configured settings + if (not ((ifaceobj.flags & iface.HAS_SIBLINGS) and + (ifaceobj.flags & iface.OLDEST_SIBLING)) and + not config_val): + continue + # if we got this far, we need to change it if config_val and (config_val != running_val): # if the configured value is not set, set it @@ -82,17 +112,17 @@ class ethtool(moduleBase,utilsBase): # no value set nor default, leave it alone pass if cmd: - self.logger.debug('ethtool %s: iface %s cmd is %s' % \ - (operation, ifaceobj.name, cmd)) try: # we should only be calling ethtool if there # is a speed set or we can find a default speed # because we should only be calling ethtool on swp ports + # we also need to set this here in case we changed + # something. this prevents unconfigured ifaces from resetting to default + self.ifaceobjs_modified_configs.append(ifaceobj.name) cmd = 'ethtool -s %s %s' %(ifaceobj.name, cmd) - self.exec_command(cmd) + utils.exec_command(cmd) except Exception, e: - ifaceobj.status = ifaceStatus.ERROR - self.log_warn('%s: %s' %(ifaceobj.name, str(e))) + self.log_error('%s: %s' %(ifaceobj.name, str(e)), ifaceobj) else: pass @@ -110,26 +140,29 @@ class ethtool(moduleBase,utilsBase): (the default will get set). """ for attr in ['speed', 'duplex', 'autoneg']: - # autoneg comes from ethtool whereas speed and duplex from /sys/class - if attr == 'autoneg': - output = self.exec_commandl(['ethtool', ifaceobj.name]) - running_attr = self.get_autoneg(ethtool_output=output) - else: - running_attr = self.read_file_oneline('/sys/class/net/%s/%s' % \ - (ifaceobj.name, attr)) - configured = ifaceobj.get_attr_value_first('link-%s'%attr) + # if there is nothing configured, do not check + if not configured: + if not ifupdownflags.flags.WITHDEFAULTS: + continue default = policymanager.policymanager_api.get_iface_default( module_name='ethtool', ifname=ifaceobj.name, attr='link-%s'%attr) - - # there is a case where there is no running config or - # (there is no default and it is not configured). - # In this case, we do nothing (e.g. eth0 has only a - # default duplex, lo has nothing) - if (not running_attr or (not configured and not default)): + # if we have no default, do not bother checking + # this avoids ethtool calls on virtual interfaces + if not default: continue + # autoneg comes from ethtool whereas speed and duplex from /sys/class + running_attr = self.get_running_attr(attr, ifaceobj) + if (not running_attr): + continue + + if attr == 'autoneg': + if configured == 'yes' and running_attr == 'on': + running_attr = 'yes' + elif configured == 'no' and running_attr == 'off': + running_attr = 'no' # we make sure we can get a running value first if (running_attr and configured and running_attr == configured): @@ -161,6 +194,25 @@ class ethtool(moduleBase,utilsBase): else: return(None) + def get_running_attr(self,attr='',ifaceobj=None): + if not ifaceobj or not attr: + return + running_attr = None + try: + if attr == 'autoneg': + output = utils.exec_commandl(['ethtool', ifaceobj.name]) + running_attr = self.get_autoneg(ethtool_output=output) + else: + running_attr = self.read_file_oneline('/sys/class/net/%s/%s' % \ + (ifaceobj.name, attr)) + except Exception as e: + # for nonexistent interfaces, we get an error (rc = 256 or 19200) + self.logger.debug('ethtool: problems calling ethtool or reading' + ' /sys/class on iface %s for attr %s: %s' % + (ifaceobj.name, attr, str(e))) + return running_attr + + def _query_running(self, ifaceobj, ifaceobj_getfunc=None): """ _query_running looks at the speed and duplex from /sys/class @@ -173,29 +225,44 @@ class ethtool(moduleBase,utilsBase): if not self.ipcmd.is_link_up(ifaceobj.name): return for attr in ['speed', 'duplex', 'autoneg']: - # autoneg comes from ethtool whereas speed and duplex from /sys/class - running_attr = None - try: - if attr == 'autoneg': - output=self.exec_commandl(['ethtool', ifaceobj.name]) - running_attr = self.get_autoneg(ethtool_output=output) - else: - running_attr = self.read_file_oneline('/sys/class/net/%s/%s' % \ - (ifaceobj.name, attr)) - except: - # for nonexistent interfaces, we get an error (rc = 256 or 19200) - pass - - # show it - if (running_attr): + default_val = policymanager.policymanager_api.get_iface_default( + module_name='ethtool', + ifname=ifaceobj.name, + attr='link-%s'%attr) + # do not continue if we have no defaults + # this avoids ethtool calls on virtual interfaces + if not default_val: + continue + running_attr = self.get_running_attr(attr, ifaceobj) + # Only show the link attributes if they differ from defaults + # to see the defaults, we should implement another flag (--with-defaults) + if default_val == running_attr: + continue + if running_attr: ifaceobj.update_config('link-%s'%attr, running_attr) return + def _query(self, ifaceobj, **kwargs): + """ add default policy attributes supported by the module """ + if ifaceobj.link_kind: + return + for attr in ['speed', 'duplex', 'autoneg']: + if ifaceobj.get_attr_value_first('link-%s'%attr): + continue + default = policymanager.policymanager_api.get_iface_default( + module_name='ethtool', + ifname=ifaceobj.name, + attr='link-%s' %attr) + if not default: + continue + ifaceobj.update_config('link-%s' %attr, default) + _run_ops = {'pre-down' : _pre_down, 'post-up' : _post_up, 'query-checkcurr' : _query_check, - 'query-running' : _query_running } + 'query-running' : _query_running, + 'query' : _query} def get_ops(self): """ returns list of ops supported by this module """ @@ -203,7 +270,7 @@ class ethtool(moduleBase,utilsBase): def _init_command_handlers(self): if not self.ipcmd: - self.ipcmd = iproute2(**self.get_flags()) + self.ipcmd = iproute2() def run(self, ifaceobj, operation, query_ifaceobj=None, **extra_args): """ run ethtool configuration on the interface object passed as @@ -227,13 +294,6 @@ class ethtool(moduleBase,utilsBase): return self._init_command_handlers() - # check to make sure we are only checking/setting interfaces with - # no lower interfaces. No bridges, no vlans, loopbacks. - if ifaceobj.lowerifaces != None or \ - self.ipcmd.link_isloopback(ifaceobj.name) or \ - self.ipcmd.is_vlan_device_by_name(ifaceobj.name): - return - if operation == 'query-checkcurr': op_handler(self, ifaceobj, query_ifaceobj) else: diff --git a/addons/link.py b/addons/link.py new file mode 100644 index 0000000..1682000 --- /dev/null +++ b/addons/link.py @@ -0,0 +1,79 @@ +#!/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 ifupdown.ifupdownflags as ifupdownflags +import logging + +class link(moduleBase): + _modinfo = {'mhelp' : 'create/configure link types. similar to ip-link', + 'attrs' : { + 'link-type' : + {'help' : 'type of link as in \'ip link\' command.', + 'validvals' : ['dummy', 'veth'], + 'example' : ['link-type ']}}} + + def __init__(self, *args, **kargs): + moduleBase.__init__(self, *args, **kargs) + self.ipcmd = None + + def _is_my_interface(self, ifaceobj): + if ifaceobj.get_attr_value_first('link-type'): + return True + return False + + def _up(self, ifaceobj): + self.ipcmd.link_create(ifaceobj.name, + ifaceobj.get_attr_value_first('link-type')) + + def _down(self, ifaceobj): + if (not ifupdownflags.flags.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): + ifaceobjcurr.update_config_with_status('link-type', 'None', 1) + else: + link_type = ifaceobj.get_attr_value_first('link-type') + if self.ipcmd.link_get_kind(ifaceobj.name) == link_type: + ifaceobjcurr.update_config_with_status('link-type', + link_type, 0) + else: + ifaceobjcurr.update_config_with_status('link-type', + link_type, 1) + + _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() + + 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_my_interface(ifaceobj)): + return + self._init_command_handlers() + if operation == 'query-checkcurr': + op_handler(self, ifaceobj, query_ifaceobj) + else: + op_handler(self, ifaceobj) diff --git a/addons/mstpctl.py b/addons/mstpctl.py index bc2cd07..442c63f 100644 --- a/addons/mstpctl.py +++ b/addons/mstpctl.py @@ -4,13 +4,16 @@ # Author: Roopa Prabhu, roopa@cumulusnetworks.com # +import os 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 +from ifupdownaddons.systemutils import systemUtils +import ifupdown.ifupdownflags as ifupdownflags +import ifupdown.policymanager as policymanager class mstpctlFlags: PORT_PROCESSED = 0x1 @@ -22,11 +25,16 @@ class mstpctl(moduleBase): 'attrs' : { 'mstpctl-ports' : {'help' : 'mstp ports', - 'compat' : True}, + 'compat' : True, + 'deprecated': True, + 'new-attribute': 'bridge-ports'}, 'mstpctl-stp' : {'help': 'bridge stp yes/no', + 'validvals' : ['yes', 'no'], 'compat' : True, - 'default' : 'no'}, + 'default' : 'no', + 'deprecated': True, + 'new-attribute': 'bridge-stp'}, 'mstpctl-treeprio' : {'help': 'tree priority', 'default' : '32768', @@ -35,75 +43,94 @@ class mstpctl(moduleBase): 'example' : ['mstpctl-treeprio 32768']}, 'mstpctl-ageing' : {'help': 'ageing time', + 'validrange' : ['0', '4096'], 'default' : '300', 'required' : False, 'example' : ['mstpctl-ageing 300']}, 'mstpctl-maxage' : { 'help' : 'max message age', + 'validrange' : ['0', '255'], 'default' : '20', 'required' : False, 'example' : ['mstpctl-maxage 20']}, 'mstpctl-fdelay' : { 'help' : 'set forwarding delay', + 'validrange' : ['0', '255'], 'default' : '15', 'required' : False, 'example' : ['mstpctl-fdelay 15']}, 'mstpctl-maxhops' : { 'help' : 'bridge max hops', + 'validrange' : ['0', '255'], 'default' : '15', 'required' : False, 'example' : ['mstpctl-maxhops 15']}, 'mstpctl-txholdcount' : { 'help' : 'bridge transmit holdcount', + 'validrange' : ['0', '255'], 'default' : '6', 'required' : False, 'example' : ['mstpctl-txholdcount 6']}, 'mstpctl-forcevers' : { 'help' : 'bridge force stp version', + 'validvals' : ['rstp', ], 'default' : 'rstp', 'required' : False, 'example' : ['mstpctl-forcevers rstp']}, 'mstpctl-portpathcost' : { 'help' : 'bridge port path cost', + 'validrange' : ['0', '65535'], 'default' : '0', + 'jsonAttr' : 'adminExtPortCost', 'required' : False, - 'example' : ['mstpctl-portpathcost swp1=0 swp2=1']}, + 'example' : ['under the bridge: mstpctl-portpathcost swp1=0 swp2=1', + 'under the port (recommended): mstpctl-portpathcost 0']}, 'mstpctl-portp2p' : { 'help' : 'bridge port p2p detection mode', 'default' : 'auto', + 'jsonAttr' : 'adminPointToPoint', 'validvals' : ['yes', 'no', 'auto'], 'required' : False, - 'example' : ['mstpctl-portp2p swp1=no swp2=no']}, + 'example' : ['under the bridge: mstpctl-portp2p swp1=yes swp2=no', + 'under the port (recommended): mstpctl-portp2p yes']}, 'mstpctl-portrestrrole' : { 'help' : 'enable/disable port ability to take root role of the port', 'default' : 'no', + 'jsonAttr' : 'restrictedRole', 'validvals' : ['yes', 'no'], 'required' : False, - 'example' : ['mstpctl-portrestrrole swp1=no swp2=no']}, + 'example' : ['under the bridge: mstpctl-portrestrrole swp1=yes swp2=no', + 'under the port (recommended): mstpctl-portrestrrole yes']}, 'mstpctl-portrestrtcn' : { 'help' : 'enable/disable port ability to propagate received topology change notification of the port', 'default' : 'no', + 'jsonAttr' : 'restrictedTcn', 'validvals' : ['yes', 'no'], 'required' : False, - 'example' : ['mstpctl-portrestrtcn swp1=no swp2=no']}, + 'example' : ['under the bridge: mstpctl-portrestrtcn swp1=yes swp2=no', + 'under the port (recommended): mstpctl-portrestrtcn yes']}, 'mstpctl-bpduguard' : { 'help' : 'enable/disable bpduguard', 'default' : 'no', + 'jsonAttr' : 'bpduGuardPort', 'validvals' : ['yes', 'no'], 'required' : False, - 'example' : ['mstpctl-bpduguard swp1=no swp2=no']}, + 'example' : ['under the bridge: mstpctl-bpduguard swp1=yes swp2=no', + 'under the port (recommended): mstpctl-bpduguard yes']}, 'mstpctl-treeportprio' : { 'help' : 'port priority for MSTI instance', 'default' : '128', 'validrange' : ['0', '240'], 'required' : False, - 'example' : ['mstpctl-treeportprio swp1=128 swp2=128']}, + 'example' : ['under the bridge: mstpctl-treeportprio swp1=128 swp2=128', + 'under the port (recommended): mstpctl-treeportprio 128']}, 'mstpctl-hello' : { 'help' : 'set hello time', + 'validrange' : ['0', '255'], 'default' : '2', 'required' : False, 'example' : ['mstpctl-hello 2']}, @@ -111,28 +138,36 @@ class mstpctl(moduleBase): { 'help' : 'enable/disable bridge assurance capability for a port', 'validvals' : ['yes', 'no'], 'default' : 'no', + 'jsonAttr' : 'networkPort', 'required' : False, - 'example' : ['mstpctl-portnetwork swp1=no swp2=no']}, + 'example' : ['under the bridge: mstpctl-portnetwork swp1=yes swp2=no', + 'under the port (recommended): mstpctl-portnetwork yes']}, 'mstpctl-portadminedge' : { 'help' : 'enable/disable initial edge state of the port', 'validvals' : ['yes', 'no'], 'default' : 'no', + 'jsonAttr' : 'adminEdgePort', 'required' : False, - 'example' : ['mstpctl-portadminedge swp1=no swp2=no']}, + 'example' : ['under the bridge: mstpctl-portadminedge swp1=yes swp2=no', + 'under the port (recommended): mstpctl-portadminedge yes']}, 'mstpctl-portautoedge' : { 'help' : 'enable/disable auto transition to/from edge state of the port', 'validvals' : ['yes', 'no'], 'default' : 'yes', + 'jsonAttr' : 'autoEdgePort', 'required' : False, - 'example' : ['mstpctl-portautoedge swp1=yes swp2=yes']}, + 'example' : ['under the bridge: mstpctl-portautoedge swp1=yes swp2=no', + 'under the port (recommended): mstpctl-portautoedge yes']}, 'mstpctl-treeportcost' : { 'help' : 'port tree cost', + 'validrange' : ['0', '255'], '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'], + 'jsonAttr' : 'bpduFilterPort', 'default' : 'no', 'required' : False, 'example' : ['under a bridge: mstpctl-portbpdufilter swp1=no swp2=no', @@ -143,8 +178,8 @@ class mstpctl(moduleBase): # 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-maxage' , 'maxage'), ('mstpctl-maxhops' , 'maxhops'), ('mstpctl-txholdcount' , 'txholdcount'), ('mstpctl-forcevers', 'forcevers'), @@ -170,6 +205,13 @@ class mstpctl(moduleBase): self.name = self.__class__.__name__ self.brctlcmd = None self.mstpctlcmd = None + self.mstpd_running = (True if systemUtils.is_process_running('mstpd') + else False) + self.default_vxlan_ports_set_bpduparams = policymanager.policymanager_api.get_module_globals(module_name=self.__class__.__name__, attr='mstpctl-vxlan-always-set-bpdu-params') + if self.default_vxlan_ports_set_bpduparams == 'yes': + self.default_vxlan_ports_set_bpduparams = True + else: + self.default_vxlan_ports_set_bpduparams = False def _is_bridge(self, ifaceobj): if (ifaceobj.get_attr_value_first('mstpctl-ports') or @@ -185,7 +227,8 @@ class mstpctl(moduleBase): 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( + return self.parse_port_list(ifaceobj.name, + ifaceobj.get_attr_value_first( 'mstpctl-ports'), ifacenames_all) def get_dependent_ifacenames_running(self, ifaceobj): @@ -205,7 +248,7 @@ class mstpctl(moduleBase): return port_list ports = ifaceobj.get_attr_value_first('mstpctl-ports') if ports: - return self.parse_port_list(ports) + return self.parse_port_list(ifaceobj.name, ports) else: return None @@ -223,7 +266,7 @@ class mstpctl(moduleBase): runningbridgeports = [] # Delete active ports not in the new port list - if not self.PERFMODE: + if not ifupdownflags.flags.PERFMODE: runningbridgeports = self.brctlcmd.get_bridge_ports(ifaceobj.name) if runningbridgeports: [self.ipcmd.link_set(bport, 'nomaster') @@ -236,7 +279,8 @@ class mstpctl(moduleBase): err = 0 for bridgeport in Set(bridgeports).difference(Set(runningbridgeports)): try: - if not self.DRYRUN and not self.ipcmd.link_exists(bridgeport): + if (not ifupdownflags.flags.DRYRUN and + not self.ipcmd.link_exists(bridgeport)): self.log_warn('%s: bridge port %s does not exist' %(ifaceobj.name, bridgeport)) err += 1 @@ -244,13 +288,13 @@ class mstpctl(moduleBase): self.ipcmd.link_set(bridgeport, 'master', ifaceobj.name) self.ipcmd.addr_flush(bridgeport) except Exception, e: - self.log_error(str(e)) + self.log_error(str(e), ifaceobj) if err: self.log_error('error configuring bridge (missing ports)') def _apply_bridge_settings(self, ifaceobj): - check = False if self.PERFMODE else True + check = False if ifupdownflags.flags.PERFMODE else True try: # set bridge attributes for attrname, dstattrname in self._attrs_map.items(): @@ -270,57 +314,138 @@ class mstpctl(moduleBase): # set bridge port attributes for attrname, dstattrname in self._port_attrs_map.items(): - attrval = ifaceobj.get_attr_value_first(attrname) - if not attrval: + config_val = ifaceobj.get_attr_value_first(attrname) + default_val = self.get_mod_subattr(attrname,'default') + if not config_val: + # nothing configured, we may need to reset all ports to defaults + # if the default exists and jsonAttribute conversion exists + try: + jsonAttr = self.get_mod_subattr(attrname, 'jsonAttr') + if default_val and jsonAttr: + bridgeports = self._get_bridge_port_list(ifaceobj) + for port in bridgeports: + if not self.brctlcmd.is_bridge_port(port): + continue + running_val = self.mstpctlcmd.get_mstpctl_bridgeport_attr(ifaceobj.name, + port, jsonAttr) + if running_val != default_val: + # we will not bother checking since we already checked + self.mstpctlcmd.set_bridgeport_attr(ifaceobj.name, + port, dstattrname, default_val, False) + except: + self.logger.info('%s: not resetting %s config' + %(ifaceobj.name, attrname)) + # leave the loop for this attribute continue - portlist = self.parse_port_list(attrval) + + portlist = self.parse_port_list(ifaceobj.name, config_val) if not portlist: - self.log_warn('%s: error parsing \'%s %s\'' - %(ifaceobj.name, attrname, attrval)) + self.log_error('%s: error parsing \'%s %s\'' + %(ifaceobj.name, attrname, config_val), ifaceobj) continue + # there was a configured value so we need to parse it + # and set the attribute for each port configured for p in portlist: try: (port, val) = p.split('=') + # if it is not bridge port, continue + if not os.path.exists('/sys/class/net/%s/brport' %port): + continue 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))) + self.log_error('%s: error setting %s (%s)' + %(ifaceobj.name, attrname, str(e)), + ifaceobj, raise_error=False) except Exception, e: self.log_warn(str(e)) pass + def _get_default_val(self, attr, ifaceobj, bridgeifaceobj): + if ((attr == 'mstpctl-portbpdufilter' or + attr == 'mstpctl-bpduguard') and + self.default_vxlan_ports_set_bpduparams and + (ifaceobj.link_kind & ifaceLinkKind.VXLAN)): + try: + config_val = bridgeifaceobj.get_attr_value_first(attr) + except Exception, e: + config_val = None + if config_val: + if ifaceobj.name not in [v.split('=')[0] for v in config_val.split()]: + return 'yes' + else: + index = [v.split('=')[0] for v in config_val.split()].index(ifaceobj.name) + return [v.split('=')[1] for v in config_val.split()][index] + else: + return 'yes' + else: + return self.get_mod_subattr(attr,'default') + def _apply_bridge_port_settings(self, ifaceobj, bridgename=None, - bridgeifaceobj=None, stp_on=True, + bridgeifaceobj=None, + stp_running_on=True, mstpd_running=True): - check = False if self.PERFMODE else True + check = False if ifupdownflags.flags.PERFMODE else True + applied = False if not bridgename and bridgeifaceobj: bridgename = bridgeifaceobj.name + + if not stp_running_on: + # stp may get turned on at a later point + self.logger.info('%s: ignoring config' + %(ifaceobj.name) + + ' (stp on bridge %s is not on yet)' %bridgename) + return applied + bvlan_aware = self.ipcmd.bridge_is_vlan_aware(bridgename) + if (not mstpd_running or + not os.path.exists('/sys/class/net/%s/brport' %ifaceobj.name) or + not bvlan_aware): + if (not bvlan_aware and + self.default_vxlan_ports_set_bpduparams and + (ifaceobj.link_kind & ifaceLinkKind.VXLAN)): + for attr in ['mstpctl-portbpdufilter', + 'mstpctl-bpduguard']: + config_val = self._get_default_val(attr, ifaceobj, bridgeifaceobj) + try: + self.mstpctlcmd.set_bridgeport_attr(bridgename, + ifaceobj.name, self._port_attrs_map[attr], + config_val, check) + except Exception, e: + self.log_warn('%s: error setting %s (%s)' + %(ifaceobj.name, attr, str(e))) + return applied # 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 + config_val = ifaceobj.get_attr_value_first(attrname) + default_val = self._get_default_val(attrname, ifaceobj, bridgeifaceobj) + jsonAttr = self.get_mod_subattr(attrname, 'jsonAttr') + # to see the running value, stp would have to be on + # so we would have parsed mstpctl showportdetail json output + try: + running_val = self.mstpctlcmd.get_mstpctl_bridgeport_attr(bridgename, + ifaceobj.name, jsonAttr) + except: + self.logger.info('%s %s: could not get running %s value' + %(bridgename, ifaceobj.name, attrname)) + running_val = None + if (not config_val and default_val and (running_val != default_val)): + # this happens when users remove an attribute from a port + # and expect the default to be restored with ifreload. + config_val = default_val + elif not config_val: + # there is nothing configured and no default to reset + continue + try: self.mstpctlcmd.set_bridgeport_attr(bridgename, - ifaceobj.name, dstattrname, attrval, check) + ifaceobj.name, dstattrname, config_val, check) + applied = True except Exception, e: - self.log_warn('%s: error setting %s (%s)' - %(ifaceobj.name, attrname, str(e))) + self.log_error('%s: error setting %s (%s)' + %(ifaceobj.name, attrname, str(e)), ifaceobj, + raise_error=False) + return applied def _apply_bridge_port_settings_all(self, ifaceobj, ifaceobj_getfunc=None): @@ -336,6 +461,8 @@ class mstpctl(moduleBase): %(ifaceobj.name, bport)) if not self.ipcmd.link_exists(bport): continue + if not os.path.exists('/sys/class/net/%s/brport' %bport): + continue bportifaceobjlist = ifaceobj_getfunc(bport) if not bportifaceobjlist: continue @@ -345,27 +472,38 @@ class mstpctl(moduleBase): mstpctlFlags.PORT_PROCESSED): continue try: - self._apply_bridge_port_settings(bportifaceobj, + self._apply_bridge_port_settings(bportifaceobj, ifaceobj.name, ifaceobj) except Exception, e: + pass self.log_warn(str(e)) + def _is_running_userspace_stp_state_on(self, bridgename): + stp_state_file = '/sys/class/net/%s/bridge/stp_state' %bridgename + if not stp_state_file: + return False + running_stp_state = self.read_file_oneline(stp_state_file) + if running_stp_state and running_stp_state == '2': + return True + return False + 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.module_flags[self.name] = ifaceobj.module_flags.setdefault(self.name,0) | \ - mstpctlFlags.PORT_PROCESSED + mstpd_running = self.mstpd_running + stp_running_on = self._is_running_userspace_stp_state_on(bridgename) + applied = self._apply_bridge_port_settings(ifaceobj, bridgename, + None, stp_running_on, + mstpd_running) + if applied: + ifaceobj.module_flags[self.name] = \ + ifaceobj.module_flags.setdefault(self.name,0) | \ + mstpctlFlags.PORT_PROCESSED return if not self._is_bridge(ifaceobj): return + # we are now here because the ifaceobj is a bridge stp = None try: porterr = False @@ -374,7 +512,7 @@ class mstpctl(moduleBase): # 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 ifupdownflags.flags.PERFMODE: if not self.ipcmd.link_exists(ifaceobj.name): self.ipcmd.link_create(ifaceobj.name, 'bridge') else: @@ -387,10 +525,10 @@ class mstpctl(moduleBase): 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') + 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: @@ -398,13 +536,13 @@ class mstpctl(moduleBase): self.brctlcmd.set_stp) else: stp = self.brctlcmd.get_stp(ifaceobj.name) - if (self.mstpctlcmd.is_mstpd_running() and + if (self.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)) + self.log_error(str(e), ifaceobj) if porterr: raise Exception(porterrstr) @@ -420,7 +558,7 @@ class mstpctl(moduleBase): self._ports_enable_disable_ipv6(ports, '0') self.brctlcmd.delete_bridge(ifaceobj.name) except Exception, e: - self.log_error(str(e)) + self.log_error(str(e), ifaceobj) def _query_running_attrs(self, ifaceobjrunning): bridgeattrdict = {} @@ -506,7 +644,8 @@ class mstpctl(moduleBase): if v}) return bridgeattrdict - def _query_check_bridge(self, ifaceobj, ifaceobjcurr): + def _query_check_bridge(self, ifaceobj, ifaceobjcurr, + ifaceobj_getfunc=None): # list of attributes that are not supported currently blacklistedattrs = ['mstpctl-portpathcost', 'mstpctl-treeportprio', 'mstpctl-treeportcost'] @@ -515,15 +654,73 @@ class mstpctl(moduleBase): return ifaceattrs = self.dict_key_subset(ifaceobj.config, self.get_mod_attrs()) + if self.default_vxlan_ports_set_bpduparams: + for attr in ['mstpctl-portbpdufilter', 'mstpctl-bpduguard']: + if attr not in ifaceattrs: + ifaceattrs.append(attr) if not ifaceattrs: return runningattrs = self.mstpctlcmd.get_bridge_attrs(ifaceobj.name) if not runningattrs: runningattrs = {} + running_port_list = self.brctlcmd.get_bridge_ports(ifaceobj.name) for k in ifaceattrs: # for all mstpctl options if k in blacklistedattrs: continue + if ((k == 'mstpctl-portbpdufilter' or + k == 'mstpctl-bpduguard')): + #special case, 'ifquery --check --with-defaults' on a VLAN + #unaware bridge + if not running_port_list: + continue + v = ifaceobj.get_attr_value_first(k) + config_val = {} + running_val = {} + result = 0 + bridge_ports = {} + state = '' + if v: + for bportval in v.split(): + config_val[bportval.split('=')[0]] = bportval.split('=')[1] + #for bport in bridgeports: + for bport in running_port_list: + bportifaceobjlist = ifaceobj_getfunc(bport) + if not bportifaceobjlist: + continue + for bportifaceobj in bportifaceobjlist: + if (bport not in config_val): + if (bportifaceobj.link_kind & ifaceLinkKind.VXLAN): + if (not ifupdownflags.flags.WITHDEFAULTS or + (ifaceobj.link_privflags & ifaceLinkPrivFlags.BRIDGE_VLAN_AWARE)): + continue + conf = 'yes' + else: + continue + else: + if ((bportifaceobj.link_kind & ifaceLinkKind.VXLAN) and + (ifaceobj.link_privflags & ifaceLinkPrivFlags.BRIDGE_VLAN_AWARE)): + continue + conf = config_val[bport] + jsonAttr = self.get_mod_subattr(k, 'jsonAttr') + try: + running_val = self.mstpctlcmd.get_mstpctl_bridgeport_attr(ifaceobj.name, bport, jsonAttr) + except: + self.logger.info('%s %s: could not get running %s value' + %(ifaceobj.name, bport, attr)) + running_val = None + if conf != running_val: + result = 1 + bridge_ports.update({bport : running_val}) + for port, val in bridge_ports.items(): + #running state format + #mstpctl-portbpdufilter swp2=yes swp1=yes vx-14567101=yes [pass] + #mstpctl-bpduguard swp2=yes swp1=yes vx-14567101=yes [pass] + state += port + '=' + val + ' ' + if state: + ifaceobjcurr.update_config_with_status(k, state, result) + continue + # get the corresponding ifaceobj attr v = ifaceobj.get_attr_value_first(k) if not v: @@ -548,7 +745,6 @@ class mstpctl(moduleBase): # 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 @@ -571,7 +767,7 @@ class mstpctl(moduleBase): # = status = 0 currstr = '' - vlist = self.parse_port_list(v) + vlist = self.parse_port_list(ifaceobj.name, v) if not vlist: continue for vlistitem in vlist: @@ -596,6 +792,46 @@ class mstpctl(moduleBase): else: ifaceobjcurr.update_config_with_status(k, rv, 0) + def _query_check_bridge_vxlan_port(self, ifaceobj, ifaceobjcurr, + ifaceobj_getfunc=None): + masters = ifaceobj.upperifaces + if not masters: + return + for bridge in masters: + bifaceobjlist = ifaceobj_getfunc(bridge) + for bifaceobj in bifaceobjlist: + if (self._is_bridge(bifaceobj) and + self.default_vxlan_ports_set_bpduparams and + (bifaceobj.link_privflags & ifaceLinkPrivFlags.BRIDGE_VLAN_AWARE)): + for attr in ['mstpctl-portbpdufilter', + 'mstpctl-bpduguard']: + jsonAttr = self.get_mod_subattr(attr, 'jsonAttr') + config_val = bifaceobj.get_attr_value_first(attr) + if config_val: + if ifaceobj.name not in [v.split('=')[0] for v in config_val.split()]: + if not ifupdownflags.flags.WITHDEFAULTS: + continue + config_val = 'yes' + else: + index = [v.split('=')[0] for v in config_val.split()].index(ifaceobj.name) + config_val = [v.split('=')[1] for v in config_val.split()][index] + else: + if not ifupdownflags.flags.WITHDEFAULTS: + continue + config_val = 'yes' + try: + running_val = self.mstpctlcmd.get_mstpctl_bridgeport_attr(bifaceobj.name, + ifaceobj.name, jsonAttr) + except: + self.logger.info('%s %s: could not get running %s value' + %(bifaceobj.name, ifaceobj.name, attr)) + running_val = None + ifaceobjcurr.update_config_with_status(attr, + running_val, + 0 if running_val == config_val else 1) + return + + 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) @@ -637,7 +873,10 @@ class mstpctl(moduleBase): def _query_check(self, ifaceobj, ifaceobjcurr, ifaceobj_getfunc=None): if self._is_bridge(ifaceobj): - self._query_check_bridge(ifaceobj, ifaceobjcurr) + self._query_check_bridge(ifaceobj, ifaceobjcurr, ifaceobj_getfunc) + elif ifaceobj.link_kind & ifaceLinkKind.VXLAN: + self._query_check_bridge_vxlan_port(ifaceobj, ifaceobjcurr, + ifaceobj_getfunc) else: self._query_check_bridge_port(ifaceobj, ifaceobjcurr) @@ -723,23 +962,71 @@ class mstpctl(moduleBase): elif self.brctlcmd.is_bridge_port(ifaceobjrunning.name): self._query_running_bridge_port(ifaceobjrunning) + def _query(self, ifaceobj, ifaceobj_getfunc=None, **kwargs): + """ add default policy attributes supported by the module """ + if not self._is_bridge(ifaceobj): + return + lowerinfs = ifaceobj.lowerifaces + if not lowerinfs: + return + if ifaceobj.get_attr_value_first('bridge-vlan-aware') != 'yes': + for attr in ['mstpctl-portbpdufilter', 'mstpctl-bpduguard']: + state = '' + config = ifaceobj.get_attr_value_first(attr) + for port in lowerinfs: + bportobjlist = ifaceobj_getfunc(port) + for bportobj in bportobjlist: + if bportobj.get_attr_value_first('vxlan-id'): + if config: + if port not in [v.split('=')[0] for v in config.split()]: + config += ' %s=yes' %port + else: + state += '%s=yes ' %port + ifaceobj.replace_config(attr, config if config else state) + else: + for attr in ['mstpctl-portbpdufilter', 'mstpctl-bpduguard']: + state = '' + config = ifaceobj.get_attr_value_first(attr) + for port in lowerinfs: + bportobjlist = ifaceobj_getfunc(port) + for bportobj in bportobjlist: + if bportobj.get_attr_value_first('vxlan-id'): + if config: + if port not in [v.split('=')[0] for v in config.split()]: + bportobj.update_config(attr, 'yes') + else: + index = [v.split('=')[0] for v in config.split()].index(port) + state = [v.split('=')[1] for v in config.split()][index] + bportobj.update_config(attr, '%s' %state) + v = config.split() + del v[index] + config = ' '.join(v) + else: + bportobj.update_config(attr, 'yes') + if config: + ifaceobj.replace_config(attr, config) + else: + ifaceobj.replace_config(attr, '') + + + _run_ops = {'pre-up' : _up, 'post-down' : _down, 'query-checkcurr' : _query_check, - 'query-running' : _query_running} + 'query-running' : _query_running, + 'query' : _query} 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) + self.ipcmd = iproute2() if not self.brctlcmd: - self.brctlcmd = brctl(**flags) + self.brctlcmd = brctl() if not self.mstpctlcmd: - self.mstpctlcmd = mstpctlutil(**flags) + self.mstpctlcmd = mstpctlutil() def run(self, ifaceobj, operation, query_ifaceobj=None, ifaceobj_getfunc=None, **extra_args): diff --git a/addons/usercmds.py b/addons/usercmds.py index 72915ea..57e8dce 100644 --- a/addons/usercmds.py +++ b/addons/usercmds.py @@ -4,64 +4,45 @@ # Author: Roopa Prabhu, roopa@cumulusnetworks.com # -import subprocess import ifupdownaddons +from ifupdown.utils import utils +import ifupdown.ifupdownflags as ifupdownflags + 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'}, + {'help' : 'run command before bringing the interface up', + 'multiline' : True}, 'up' : - {'help' : 'run command at interface bring up'}, + {'help' : 'run command at interface bring up', + 'multiline' : True}, 'post-up' : - {'help' : 'run command after interface bring up'}, + {'help' : 'run command after interface bring up', + 'multiline' : True}, 'pre-down' : - {'help' : 'run command before bringing the interface down'}, + {'help' : 'run command before bringing the interface down', + 'multiline' : True}, 'down' : - {'help' : 'run command at interface down'}, + {'help' : 'run command at interface down', + 'multiline' : True}, '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 + {'help' : 'run command after bringing interface down', + 'multiline' : True}}} 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) + utils.exec_user_command(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'))) + self.logger.warn('%s: %s %s' % (ifaceobj.name, op, + str(e).strip('\n'))) pass _run_ops = {'pre-up' : _run_command, diff --git a/addons/vlan.py b/addons/vlan.py index c877d55..ec2e2e5 100644 --- a/addons/vlan.py +++ b/addons/vlan.py @@ -7,7 +7,10 @@ from ifupdown.iface import * from ifupdownaddons.modulebase import moduleBase from ifupdownaddons.iproute2 import iproute2 -import ifupdown.rtnetlink_api as rtnetlink_api +import ifupdown.ifupdownconfig as ifupdownConfig + +from ifupdown.netlink import netlink +import ifupdown.ifupdownflags as ifupdownflags import logging import re @@ -21,9 +24,11 @@ class vlan(moduleBase): 'attributes', 'attrs' : { 'vlan-raw-device' : - {'help' : 'vlan raw device'}, + {'help' : 'vlan raw device', + 'validvals' : ['' ,]}, 'vlan-id' : - {'help' : 'vlan id'}}} + {'help' : 'vlan id', + 'validrange' : ['0', '4096']}}} def __init__(self, *args, **kargs): @@ -91,7 +96,7 @@ class vlan(moduleBase): 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 @@ -104,11 +109,9 @@ class vlan(moduleBase): 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) + netlink.link_add_bridge_vlan(bridgename, vlanid) else: - rtnetlink_api.rtnl_api.bridge_vlan(add=False, dev=bridgename, - vid=vlanid, master=False) + netlink.link_del_bridge_vlan(bridgename, vlanid) def _bridge_vid_check(self, ifaceobj, ifaceobjcurr, bridgename, vlanid): """ If the lower device is a vlan aware bridge, check if the vlanid @@ -127,22 +130,23 @@ class vlan(moduleBase): 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 ifupdownflags.flags.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) + if ifupdownConfig.config.get('adjust_logical_dev_mtu', '1') != '0' and len(ifaceobj.lowerifaces): + lower_iface_mtu = self.ipcmd.link_get_mtu(ifaceobj.lowerifaces[0], refresh=True) + if not lower_iface_mtu == self.ipcmd.link_get_mtu(ifaceobj.name): + self.ipcmd.link_set_mtu(ifaceobj.name, lower_iface_mtu) return - rtnetlink_api.rtnl_api.create_vlan(vlanrawdevice, - ifaceobj.name, vlanid) + netlink.link_add_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") + netlink.link_set_updown(ifaceobj.name, "up") def _down(self, ifaceobj): vlanid = self._get_vlan_id(ifaceobj) @@ -151,7 +155,8 @@ class vlan(moduleBase): 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): + if (not ifupdownflags.flags.PERFMODE and + not self.ipcmd.link_exists(ifaceobj.name)): return try: self.ipcmd.link_delete(ifaceobj.name) @@ -203,8 +208,7 @@ class vlan(moduleBase): def _init_command_handlers(self): if not self.ipcmd: - self.ipcmd = iproute2(**self.get_flags()) - + self.ipcmd = iproute2() def run(self, ifaceobj, operation, query_ifaceobj=None, **extra_args): """ run vlan configuration on the interface object passed as argument diff --git a/addons/vrf.py b/addons/vrf.py new file mode 100644 index 0000000..36b222e --- /dev/null +++ b/addons/vrf.py @@ -0,0 +1,925 @@ +#!/usr/bin/python +# +# Copyright 2014 Cumulus Networks, Inc. All rights reserved. +# Author: Roopa Prabhu, roopa@cumulusnetworks.com +# + +import os +import signal +import errno +import fcntl +import atexit +from ifupdown.iface import * +from ifupdown.utils import utils +import ifupdown.policymanager as policymanager +import ifupdownaddons +from ifupdown.netlink import netlink +import ifupdown.ifupdownflags as ifupdownflags +from ifupdownaddons.modulebase import moduleBase +from ifupdownaddons.bondutil import bondutil +from ifupdownaddons.iproute2 import iproute2 +from ifupdownaddons.dhclient import dhclient +from ifupdownaddons.utilsbase import * + +class vrfPrivFlags: + PROCESSED = 0x1 + +class vrf(moduleBase): + """ ifupdown2 addon module to configure vrfs """ + _modinfo = { 'mhelp' : 'vrf configuration module', + 'attrs' : { + 'vrf-table': + {'help' : 'vrf device routing table id. key to ' + + 'creating a vrf device. ' + + 'Table id is either \'auto\' or '+ + '\'valid routing table id\'', + 'example': ['vrf-table auto', 'vrf-table 1001']}, + 'vrf': + {'help' : 'vrf the interface is part of.', + 'example': ['vrf blue']}}} + + iproute2_vrf_filename = '/etc/iproute2/rt_tables.d/ifupdown2_vrf_map.conf' + iproute2_vrf_filehdr = '# This file is autogenerated by ifupdown2.\n' + \ + '# It contains the vrf name to table mapping.\n' + \ + '# Reserved table range %s %s\n' + VRF_TABLE_START = 1001 + VRF_TABLE_END = 5000 + + system_reserved_rt_tables = {'255' : 'local', '254' : 'main', + '253' : 'default', '0' : 'unspec'} + + def __init__(self, *args, **kargs): + ifupdownaddons.modulebase.moduleBase.__init__(self, *args, **kargs) + self.ipcmd = None + self.bondcmd = None + self.dhclientcmd = None + self.name = self.__class__.__name__ + if ifupdownflags.flags.PERFMODE: + # if perf mode is set, remove vrf map file. + # start afresh. PERFMODE is set at boot + if os.path.exists(self.iproute2_vrf_filename): + try: + self.logger.info('vrf: removing file %s' + %self.iproute2_vrf_filename) + os.remove(self.iproute2_vrf_filename) + except Exception, e: + self.logger.debug('vrf: removing file failed (%s)' + %str(e)) + try: + ip_rules = utils.exec_command('/sbin/ip rule show').splitlines() + self.ip_rule_cache = [' '.join(r.split()) for r in ip_rules] + except Exception, e: + self.ip_rule_cache = [] + self.logger.warn('vrf: cache v4: %s' % str(e)) + + try: + ip_rules = utils.exec_command('/sbin/ip -6 rule show').splitlines() + self.ip6_rule_cache = [' '.join(r.split()) for r in ip_rules] + except Exception, e: + self.ip6_rule_cache = [] + self.logger.warn('vrf: cache v6: %s' % str(e)) + + #self.logger.debug("vrf: ip rule cache") + #self.logger.info(self.ip_rule_cache) + + #self.logger.info("vrf: ip -6 rule cache") + #self.logger.info(self.ip6_rule_cache) + + self._iproute2_vrf_map_initialized = False + self.iproute2_vrf_map = {} + self.iproute2_vrf_map_fd = None + self.iproute2_vrf_map_sync_to_disk = False + + self.vrf_table_id_start = policymanager.policymanager_api.get_module_globals(module_name=self.__class__.__name__, attr='vrf-table-id-start') + if not self.vrf_table_id_start: + self.vrf_table_id_start = self.VRF_TABLE_START + self.vrf_table_id_end = policymanager.policymanager_api.get_module_globals(module_name=self.__class__.__name__, attr='vrf-table-id-end') + if not self.vrf_table_id_end: + self.vrf_table_id_end = self.VRF_TABLE_END + self.vrf_max_count = policymanager.policymanager_api.get_module_globals(module_name=self.__class__.__name__, attr='vrf-max-count') + + self.vrf_fix_local_table = True + self.vrf_count = 0 + self.vrf_mgmt_devname = policymanager.policymanager_api.get_module_globals(module_name=self.__class__.__name__, attr='vrf-mgmt-devname') + self.vrf_helper = policymanager.policymanager_api.get_module_globals(module_name=self.__class__.__name__, attr='vrf-helper') + + def _iproute2_vrf_map_initialize(self, writetodisk=True): + if self._iproute2_vrf_map_initialized: + return + + # XXX: check for vrf reserved overlap in /etc/iproute2/rt_tables + self.iproute2_vrf_map = {} + iproute2_vrf_map_force_rewrite = False + # read or create /etc/iproute2/rt_tables.d/ifupdown2.vrf_map + if os.path.exists(self.iproute2_vrf_filename): + with open(self.iproute2_vrf_filename, 'r+') as vrf_map_fd: + lines = vrf_map_fd.readlines() + for l in lines: + l = l.strip() + if l[0] == '#': + continue + try: + (table, vrf_name) = l.strip().split() + if self.iproute2_vrf_map.get(int(table)): + # looks like the existing file has + # duplicate entries, force rewrite of the + # file + iproute2_vrf_map_force_rewrite = True + continue + self.iproute2_vrf_map[int(table)] = vrf_name + except Exception, e: + self.logger.info('vrf: iproute2_vrf_map: unable to parse %s' + %l) + pass + + vrfs = self.ipcmd.link_get_vrfs() + running_vrf_map = {} + if vrfs: + for v, lattrs in vrfs.iteritems(): + table = lattrs.get('table', None) + if table: + running_vrf_map[int(table)] = v + + if (not running_vrf_map or (running_vrf_map != self.iproute2_vrf_map)): + self.iproute2_vrf_map = running_vrf_map + iproute2_vrf_map_force_rewrite = True + + self.iproute2_vrf_map_fd = None + if writetodisk: + if iproute2_vrf_map_force_rewrite: + # reopen the file and rewrite the map + self._iproute2_vrf_map_open(True, False) + else: + self._iproute2_vrf_map_open(False, True) + + self.iproute2_vrf_map_sync_to_disk = False + atexit.register(self._iproute2_vrf_map_sync_to_disk) + + self.logger.info("vrf: dumping iproute2_vrf_map") + self.logger.info(self.iproute2_vrf_map) + + last_used_vrf_table = None + for t in range(self.vrf_table_id_start, + self.vrf_table_id_end): + if not self.iproute2_vrf_map.get(t): + break + last_used_vrf_table = t + self.last_used_vrf_table = last_used_vrf_table + self._iproute2_vrf_map_initialized = True + self.vrf_count = len(self.iproute2_vrf_map) + + def _iproute2_vrf_map_sync_to_disk(self): + if (ifupdownflags.flags.DRYRUN or + not self.iproute2_vrf_map_sync_to_disk): + return + self.logger.info('vrf: syncing table map to %s' + %self.iproute2_vrf_filename) + with open(self.iproute2_vrf_filename, 'w') as f: + f.write(self.iproute2_vrf_filehdr %(self.vrf_table_id_start, + self.vrf_table_id_end)) + for t, v in self.iproute2_vrf_map.iteritems(): + f.write('%s %s\n' %(t, v)) + f.flush() + + def _iproute2_vrf_map_open(self, sync_vrfs=False, append=False): + self.logger.info('vrf: syncing table map to %s' + %self.iproute2_vrf_filename) + if ifupdownflags.flags.DRYRUN: + return + fmode = 'a+' if append else 'w' + try: + self.iproute2_vrf_map_fd = open(self.iproute2_vrf_filename, + '%s' %fmode) + fcntl.fcntl(self.iproute2_vrf_map_fd, fcntl.F_SETFD, fcntl.FD_CLOEXEC) + except Exception, e: + self.log_warn('vrf: error opening %s (%s)' + %(self.iproute2_vrf_filename, str(e))) + return + + if not append: + # write file header + self.iproute2_vrf_map_fd.write(self.iproute2_vrf_filehdr + %(self.vrf_table_id_start, + self.vrf_table_id_end)) + for t, v in self.iproute2_vrf_map.iteritems(): + self.iproute2_vrf_map_fd.write('%s %s\n' %(t, v)) + self.iproute2_vrf_map_fd.flush() + + def _is_vrf(self, ifaceobj): + if ifaceobj.get_attr_value_first('vrf-table'): + return True + return False + + def get_upper_ifacenames(self, ifaceobj, ifacenames_all=None): + """ Returns list of interfaces dependent on ifaceobj """ + + vrf_table = ifaceobj.get_attr_value_first('vrf-table') + if vrf_table: + ifaceobj.link_type = ifaceLinkType.LINK_MASTER + ifaceobj.link_kind |= ifaceLinkKind.VRF + ifaceobj.role |= ifaceRole.MASTER + vrf_iface_name = ifaceobj.get_attr_value_first('vrf') + if not vrf_iface_name: + return None + ifaceobj.link_type = ifaceLinkType.LINK_SLAVE + ifaceobj.link_privflags |= ifaceLinkPrivFlags.VRF_SLAVE + + return [vrf_iface_name] + + def get_upper_ifacenames_running(self, ifaceobj): + return None + + def _get_iproute2_vrf_table(self, vrf_dev_name): + for t, v in self.iproute2_vrf_map.iteritems(): + if v == vrf_dev_name: + return str(t) + return None + + def _get_avail_vrf_table_id(self): + if self.last_used_vrf_table == None: + table_id_start = self.vrf_table_id_start + else: + table_id_start = self.last_used_vrf_table + 1 + for t in range(table_id_start, + self.vrf_table_id_end): + if not self.iproute2_vrf_map.get(t): + self.last_used_vrf_table = t + return str(t) + return None + + def _iproute2_is_vrf_tableid_inuse(self, vrfifaceobj, table_id): + old_vrf_name = self.iproute2_vrf_map.get(int(table_id)) + if old_vrf_name and old_vrf_name != vrfifaceobj.name: + self.log_error('table id %s already assigned to vrf dev %s' + %(table_id, old_vrf_name), vrfifaceobj) + + def _iproute2_vrf_table_entry_add(self, vrfifaceobj, table_id): + old_vrf_name = self.iproute2_vrf_map.get(int(table_id)) + if not old_vrf_name: + self.iproute2_vrf_map[int(table_id)] = vrfifaceobj.name + if self.iproute2_vrf_map_fd: + self.iproute2_vrf_map_fd.write('%s %s\n' + %(table_id, vrfifaceobj.name)) + self.iproute2_vrf_map_fd.flush() + self.vrf_count += 1 + return + + if old_vrf_name != vrfifaceobj.name: + self.log_error('table id %d already assigned to vrf dev %s' + %(table_id, old_vrf_name)) + + def _iproute2_vrf_table_entry_del(self, table_id): + try: + # with any del of vrf map, we need to force sync to disk + self.iproute2_vrf_map_sync_to_disk = True + del self.iproute2_vrf_map[int(table_id)] + except Exception, e: + self.logger.info('vrf: iproute2 vrf map del failed for %d (%s)' + %(table_id, str(e))) + pass + + def _is_vrf_dev(self, ifacename): + # Look at iproute2 map for now. + # If it was a master we knew about, + # it is definately there + if ifacename in self.iproute2_vrf_map.values(): + return True + return False + + def _is_dhcp_slave(self, ifaceobj): + if (not ifaceobj.addr_method or + (ifaceobj.addr_method != 'dhcp' and + ifaceobj.addr_method != 'dhcp6')): + return False + return True + + def _up_vrf_slave_without_master(self, ifacename, vrfname, ifaceobj, + ifaceobj_getfunc): + """ If we have a vrf slave that has dhcp configured, bring up the + vrf master now. This is needed because vrf has special handling + in dhclient hook which requires the vrf master to be present """ + + vrf_master = ifaceobj.upperifaces[0] + if not vrf_master: + self.logger.warn('%s: vrf master not found' %ifacename) + return + if os.path.exists('/sys/class/net/%s' %vrf_master): + self.logger.info('%s: vrf master %s exists returning' + %(ifacename, vrf_master)) + return + vrf_master_objs = ifaceobj_getfunc(vrf_master) + if not vrf_master_objs: + self.logger.warn('%s: vrf master ifaceobj not found' %ifacename) + return + self.logger.info('%s: bringing up vrf master %s' + %(ifacename, vrf_master)) + for mobj in vrf_master_objs: + vrf_table = mobj.get_attr_value_first('vrf-table') + if vrf_table: + if vrf_table == 'auto': + vrf_table = self._get_avail_vrf_table_id() + if not vrf_table: + self.log_error('%s: unable to get an auto table id' + %mobj.name, ifaceobj) + self.logger.info('%s: table id auto: selected table id %s\n' + %(mobj.name, vrf_table)) + try: + self._up_vrf_dev(mobj, vrf_table, False) + except Exception: + raise + break + self._handle_existing_connections(ifaceobj, vrfname) + self.ipcmd.link_set(ifacename, 'master', vrfname) + return + + def _down_dhcp_slave(self, ifaceobj, vrfname): + try: + dhclient_cmd_prefix = None + if (vrfname and self.vrf_exec_cmd_prefix and + self.ipcmd.link_exists(vrfname)): + dhclient_cmd_prefix = '%s %s' %(self.vrf_exec_cmd_prefix, + vrfname) + self.dhclientcmd.release(ifaceobj.name, dhclient_cmd_prefix) + except: + # ignore any dhclient release errors + pass + + def _handle_existing_connections(self, ifaceobj, vrfname): + if not ifaceobj or ifupdownflags.flags.PERFMODE: + return + if (self.vrf_mgmt_devname and + self.vrf_mgmt_devname == vrfname): + self._kill_ssh_connections(ifaceobj.name) + if self._is_dhcp_slave(ifaceobj): + self._down_dhcp_slave(ifaceobj, vrfname) + + def _up_vrf_slave(self, ifacename, vrfname, ifaceobj=None, + ifaceobj_getfunc=None, vrf_exists=False): + try: + master_exists = True + if vrf_exists or self.ipcmd.link_exists(vrfname): + upper = self.ipcmd.link_get_upper(ifacename) + if not upper or upper != vrfname: + self._handle_existing_connections(ifaceobj, vrfname) + self.ipcmd.link_set(ifacename, 'master', vrfname) + elif ifupdownflags.flags.ALL and ifaceobj: + self._up_vrf_slave_without_master(ifacename, vrfname, ifaceobj, + ifaceobj_getfunc) + else: + master_exists = False + if master_exists: + netlink.link_set_updown(ifacename, "up") + elif ifupdownflags.flags.ALL: + self.log_error('vrf %s not around, skipping vrf config' + %(vrfname), ifaceobj) + except Exception, e: + self.log_error('%s: %s' %(ifacename, str(e)), ifaceobj) + + def _del_vrf_rules(self, vrf_dev_name, vrf_table): + pref = 200 + ip_rule_out_format = '%s: from all %s %s lookup %s' + ip_rule_cmd = 'ip %s rule del pref %s %s %s table %s' + + rule = ip_rule_out_format %(pref, 'oif', vrf_dev_name, vrf_dev_name) + if rule in self.ip_rule_cache: + rule_cmd = ip_rule_cmd %('', pref, 'oif', vrf_dev_name, + vrf_dev_name) + utils.exec_command(rule_cmd) + + rule = ip_rule_out_format %(pref, 'iif', vrf_dev_name, vrf_dev_name) + if rule in self.ip_rule_cache: + rule_cmd = ip_rule_cmd %('', pref, 'iif', vrf_dev_name, + vrf_dev_name) + utils.exec_command(rule_cmd) + + rule = ip_rule_out_format %(pref, 'oif', vrf_dev_name, vrf_dev_name) + if rule in self.ip6_rule_cache: + rule_cmd = ip_rule_cmd %('-6', pref, 'oif', vrf_dev_name, + vrf_dev_name) + utils.exec_command(rule_cmd) + + rule = ip_rule_out_format %(pref, 'iif', vrf_dev_name, vrf_dev_name) + if rule in self.ip6_rule_cache: + rule_cmd = ip_rule_cmd %('-6', pref, 'iif', vrf_dev_name, + vrf_dev_name) + utils.exec_command(rule_cmd) + + def _add_vrf_rules(self, vrf_dev_name, vrf_table): + pref = 200 + ip_rule_out_format = '%s: from all %s %s lookup %s' + ip_rule_cmd = 'ip %s rule add pref %s %s %s table %s' + if self.vrf_fix_local_table: + self.vrf_fix_local_table = False + rule = '0: from all lookup local' + if rule in self.ip_rule_cache: + try: + utils.exec_command('ip rule del pref 0') + utils.exec_command('ip rule add pref 32765 table local') + except Exception, e: + self.logger.info('%s: %s' % (vrf_dev_name, str(e))) + pass + if rule in self.ip6_rule_cache: + try: + utils.exec_command('ip -6 rule del pref 0') + utils.exec_command('ip -6 rule add pref 32765 table local') + except Exception, e: + self.logger.info('%s: %s' % (vrf_dev_name, str(e))) + pass + + #Example ip rule + #200: from all oif blue lookup blue + #200: from all iif blue lookup blue + + rule = ip_rule_out_format %(pref, 'oif', vrf_dev_name, vrf_dev_name) + if rule not in self.ip_rule_cache: + rule_cmd = ip_rule_cmd %('', pref, 'oif', vrf_dev_name, + vrf_dev_name) + utils.exec_command(rule_cmd) + + rule = ip_rule_out_format %(pref, 'iif', vrf_dev_name, vrf_dev_name) + if rule not in self.ip_rule_cache: + rule_cmd = ip_rule_cmd %('', pref, 'iif', vrf_dev_name, + vrf_dev_name) + utils.exec_command(rule_cmd) + + rule = ip_rule_out_format %(pref, 'oif', vrf_dev_name, vrf_dev_name) + if rule not in self.ip6_rule_cache: + rule_cmd = ip_rule_cmd %('-6', pref, 'oif', vrf_dev_name, + vrf_dev_name) + utils.exec_command(rule_cmd) + + rule = ip_rule_out_format %(pref, 'iif', vrf_dev_name, vrf_dev_name) + if rule not in self.ip6_rule_cache: + rule_cmd = ip_rule_cmd %('-6', pref, 'iif', vrf_dev_name, + vrf_dev_name) + utils.exec_command(rule_cmd) + + def _add_vrf_slaves(self, ifaceobj, ifaceobj_getfunc=None): + running_slaves = self.ipcmd.link_get_lowers(ifaceobj.name) + config_slaves = ifaceobj.lowerifaces + if not config_slaves and not running_slaves: + return + + if not config_slaves: config_slaves = [] + if not running_slaves: running_slaves = [] + add_slaves = set(config_slaves).difference(set(running_slaves)) + del_slaves = set(running_slaves).difference(set(config_slaves)) + if add_slaves: + for s in add_slaves: + try: + if not self.ipcmd.link_exists(s): + continue + sobj = None + if ifaceobj_getfunc: + sobj = ifaceobj_getfunc(s) + self._up_vrf_slave(s, ifaceobj.name, + sobj[0] if sobj else None, + ifaceobj_getfunc, True) + except Exception, e: + self.logger.info('%s: %s' %(ifaceobj.name, str(e))) + + if del_slaves: + for s in del_slaves: + try: + sobj = None + if ifaceobj_getfunc: + sobj = ifaceobj_getfunc(s) + self._down_vrf_slave(s, sobj[0] if sobj else None, + ifaceobj.name) + except Exception, e: + self.logger.info('%s: %s' %(ifaceobj.name, str(e))) + + if ifaceobj.link_type == ifaceLinkType.LINK_MASTER: + for s in config_slaves: + try: + netlink.link_set_updown(s, "up") + except Exception, e: + self.logger.debug('%s: %s' % (ifaceobj.name, str(e))) + pass + + def _set_vrf_dev_processed_flag(self, ifaceobj): + ifaceobj.module_flags[self.name] = \ + ifaceobj.module_flags.setdefault(self.name, 0) | \ + vrfPrivFlags.PROCESSED + + def _check_vrf_dev_processed_flag(self, ifaceobj): + if (ifaceobj.module_flags.get(self.name, 0) & vrfPrivFlags.PROCESSED): + return True + return False + + def _create_vrf_dev(self, ifaceobj, vrf_table): + if not self.ipcmd.link_exists(ifaceobj.name): + if ifaceobj.name in self.system_reserved_rt_tables.values(): + self.log_error('cannot use system reserved %s vrf names' + %(str(self.system_reserved_rt_tables.values())), + ifaceobj) + if self.vrf_count == self.vrf_max_count: + self.log_error('%s: max vrf count %d hit...not ' + 'creating vrf' %(ifaceobj.name, + self.vrf_count), ifaceobj) + if vrf_table == 'auto': + vrf_table = self._get_avail_vrf_table_id() + if not vrf_table: + self.log_error('%s: unable to get an auto table id' + %ifaceobj.name, ifaceobj) + self.logger.info('%s: table id auto: selected table id %s\n' + %(ifaceobj.name, vrf_table)) + else: + self._iproute2_is_vrf_tableid_inuse(ifaceobj, vrf_table) + if ifaceobj.name in self.system_reserved_rt_tables.keys(): + self.log_error('cannot use system reserved %s table ids' + %(str(self.system_reserved_rt_tables.keys())), + ifaceobj) + + if not vrf_table.isdigit(): + self.log_error('%s: vrf-table must be an integer or \'auto\'' + %(ifaceobj.name), ifaceobj) + + # XXX: If we decide to not allow vrf id usages out of + # the reserved ifupdown range, then uncomment this code. + else: + if (int(vrf_table) < self.vrf_table_id_start or + int(vrf_table) > self.vrf_table_id_end): + self.log_error('%s: vrf table id %s out of reserved range [%d,%d]' + %(ifaceobj.name, vrf_table, + self.vrf_table_id_start, + self.vrf_table_id_end), ifaceobj) + try: + self.ipcmd.link_create(ifaceobj.name, 'vrf', + {'table' : '%s' %vrf_table}) + except Exception, e: + self.log_error('%s: create failed (%s)\n' + %(ifaceobj.name, str(e)), ifaceobj) + if vrf_table != 'auto': + self._iproute2_vrf_table_entry_add(ifaceobj, vrf_table) + else: + if vrf_table == 'auto': + vrf_table = self._get_iproute2_vrf_table(ifaceobj.name) + if not vrf_table: + self.log_error('%s: unable to get vrf table id' + %ifaceobj.name, ifaceobj) + + # if the device exists, check if table id is same + vrfdev_attrs = self.ipcmd.link_get_linkinfo_attrs(ifaceobj.name) + if vrfdev_attrs: + running_table = vrfdev_attrs.get('table', None) + if vrf_table != running_table: + self.log_error('%s: cannot change vrf table id,running table id %s is different from config id %s' %(ifaceobj.name, + running_table, vrf_table), + ifaceobj) + return vrf_table + + def _up_vrf_helper(self, ifaceobj, vrf_table): + mode = "" + if ifupdownflags.flags.PERFMODE: + mode = "boot" + if self.vrf_helper: + utils.exec_command('%s create %s %s %s' % + (self.vrf_helper, + ifaceobj.name, + vrf_table, + mode)) + + def _up_vrf_dev(self, ifaceobj, vrf_table, add_slaves=True, + ifaceobj_getfunc=None): + + # if vrf dev is already processed return. This can happen + # if we the slave was configured before. + # see self._up_vrf_slave_without_master + if self._check_vrf_dev_processed_flag(ifaceobj): + return True + + try: + vrf_table = self._create_vrf_dev(ifaceobj, vrf_table) + except Exception, e: + self.log_error('%s: %s' %(ifaceobj.name, str(e)), ifaceobj) + + try: + self._add_vrf_rules(ifaceobj.name, vrf_table) + self._up_vrf_helper(ifaceobj, vrf_table) + if add_slaves: + self._add_vrf_slaves(ifaceobj, ifaceobj_getfunc) + self._set_vrf_dev_processed_flag(ifaceobj) + netlink.link_set_updown(ifaceobj.name, "up") + except Exception, e: + self.log_error('%s: %s' %(ifaceobj.name, str(e)), ifaceobj) + + def _kill_ssh_connections(self, ifacename): + try: + runningaddrsdict = self.ipcmd.addr_get(ifacename) + if not runningaddrsdict: + return + iplist = [i.split('/', 1)[0] for i in runningaddrsdict.keys()] + if not iplist: + return + proc=[] + #Example output: + #ESTAB 0 0 10.0.1.84:ssh 10.0.1.228:45186 + #users:(("sshd",pid=2528,fd=3)) + cmdl = ['/bin/ss', '-t', '-p'] + for line in utils.exec_commandl(cmdl).splitlines(): + citems = line.split() + addr = None + if '%' in citems[3]: + addr = citems[3].split('%')[0] + elif ':ssh' in citems[3]: + addr = citems[3].split(':')[0] + if not addr: + continue + if addr in iplist: + if len(citems) == 6: + proc.append(citems[5].split(',')[1].split('=')[1]) + + if not proc: + return + pid = None + # outpt of '/usr/bin/pstree -Aps ': + # 'systemd(1)---sshd(990)---sshd(16112)---sshd(16126)---bash(16127)---sudo(16756)---ifreload(16761)---pstree(16842)\n' + # get the above output to following format + # ['systemd(1)', 'sshd(990)', 'sshd(16112)', 'sshd(16126)', 'bash(16127)', 'sudo(16756)', 'ifreload(16761)', 'pstree(16850)'] + pstree = list(reversed(utils.exec_command('/usr/bin/pstree -Aps %s' %os.getpid()).strip().split('---'))) + for index, process in enumerate(pstree): + # check the parent of SSH process to make sure + # we don't kill SSH server or systemd process + if 'sshd' in process and 'sshd' in pstree[index + 1]: + pid = filter(lambda x: x.isdigit(), process) + break + self.logger.info("%s: killing active ssh sessions: %s" + %(ifacename, str(proc))) + + if ifupdownflags.flags.DRYRUN: + return + for id in proc: + if id != pid: + try: + os.kill(int(id), signal.SIGINT) + except OSError as e: + continue + + # Kill current SSH client + if pid in proc: + try: + forkret = os.fork() + except OSError, e: + self.logger.info("fork error : %s [%d]" % (e.strerror, e.errno)) + if (forkret == 0): # The first child. + try: + os.setsid() + self.logger.info("%s: ifreload continuing in the background" %ifacename) + except OSError, (err_no, err_message): + self.logger.info("os.setsid failed: errno=%d: %s" % (err_no, err_message)) + self.logger.info("pid=%d pgid=%d" % (os.getpid(), os.getpgid(0))) + try: + self.logger.info("%s: killing our session: %s" + %(ifacename, str(proc))) + os.kill(int(pid), signal.SIGINT) + return + except OSError as e: + return + except Exception, e: + self.logger.info('%s: %s' %(ifacename, str(e))) + + def _up(self, ifaceobj, ifaceobj_getfunc=None): + try: + vrf_table = ifaceobj.get_attr_value_first('vrf-table') + if vrf_table: + self._iproute2_vrf_map_initialize() + # This is a vrf device + self._up_vrf_dev(ifaceobj, vrf_table, True, ifaceobj_getfunc) + else: + vrf = ifaceobj.get_attr_value_first('vrf') + if vrf: + self._iproute2_vrf_map_initialize() + # This is a vrf slave + self._up_vrf_slave(ifaceobj.name, vrf, ifaceobj, + ifaceobj_getfunc) + else: + # check if we were a slave before + master = self.ipcmd.link_get_master(ifaceobj.name) + if master: + self._iproute2_vrf_map_initialize() + if self._is_vrf_dev(master): + self._down_vrf_slave(ifaceobj.name, ifaceobj, + master) + except Exception, e: + self.log_error(str(e), ifaceobj) + + def _down_vrf_helper(self, ifaceobj, vrf_table): + mode = "" + if ifupdownflags.flags.PERFMODE: + mode = "boot" + if self.vrf_helper: + utils.exec_command('%s delete %s %s %s' % + (self.vrf_helper, + ifaceobj.name, + vrf_table, + mode)) + + def _down_vrf_dev(self, ifaceobj, vrf_table, ifaceobj_getfunc=None): + + if vrf_table == 'auto': + vrf_table = self._get_iproute2_vrf_table(ifaceobj.name) + + running_slaves = self.ipcmd.link_get_lowers(ifaceobj.name) + if running_slaves: + for s in running_slaves: + if ifaceobj_getfunc: + sobj = ifaceobj_getfunc(s) + try: + self._handle_existing_connections(sobj[0] + if sobj else None, + ifaceobj.name) + except Exception, e: + self.logger.info('%s: %s' %(ifaceobj.name, str(e))) + pass + try: + self.ipcmd.addr_flush(s) + netlink.link_set_updown(s, "down") + except Exception, e: + self.logger.info('%s: %s' %(ifaceobj.name, str(e))) + pass + + try: + self._down_vrf_helper(ifaceobj, vrf_table) + except Exception, e: + self.logger.warn('%s: %s' %(ifaceobj.name, str(e))) + pass + + try: + self._del_vrf_rules(ifaceobj.name, vrf_table) + except Exception, e: + self.logger.info('%s: %s' %(ifaceobj.name, str(e))) + pass + + try: + self.ipcmd.link_delete(ifaceobj.name) + except Exception, e: + self.logger.info('%s: %s' %(ifaceobj.name, str(e))) + pass + + try: + self._iproute2_vrf_table_entry_del(vrf_table) + except Exception, e: + self.logger.info('%s: %s' %(ifaceobj.name, str(e))) + pass + + + def _down_vrf_slave(self, ifacename, ifaceobj=None, vrfname=None): + try: + self._handle_existing_connections(ifaceobj, vrfname) + self.ipcmd.link_set(ifacename, 'nomaster') + netlink.link_set_updown(ifacename, "down") + except Exception, e: + self.logger.warn('%s: %s' %(ifacename, str(e))) + + def _down(self, ifaceobj, ifaceobj_getfunc=None): + try: + vrf_table = ifaceobj.get_attr_value_first('vrf-table') + if vrf_table: + self._iproute2_vrf_map_initialize() + self._down_vrf_dev(ifaceobj, vrf_table, ifaceobj_getfunc) + else: + vrf = ifaceobj.get_attr_value_first('vrf') + if vrf: + self._iproute2_vrf_map_initialize() + self._down_vrf_slave(ifaceobj.name, ifaceobj, None) + except Exception, e: + self.log_warn(str(e)) + + def _query_check_vrf_slave(self, ifaceobj, ifaceobjcurr, vrf): + try: + master = self.ipcmd.link_get_master(ifaceobj.name) + if not master or master != vrf: + ifaceobjcurr.update_config_with_status('vrf', str(master), 1) + else: + ifaceobjcurr.update_config_with_status('vrf', master, 0) + except Exception, e: + self.log_error(str(e), ifaceobjcurr) + + def _query_check_vrf_dev(self, ifaceobj, ifaceobjcurr, vrf_table): + try: + if not self.ipcmd.link_exists(ifaceobj.name): + self.logger.info('%s: vrf: does not exist' %(ifaceobj.name)) + return + if vrf_table == 'auto': + config_table = self._get_iproute2_vrf_table(ifaceobj.name) + else: + config_table = vrf_table + vrfdev_attrs = self.ipcmd.link_get_linkinfo_attrs(ifaceobj.name) + if not vrfdev_attrs: + ifaceobjcurr.update_config_with_status('vrf-table', 'None', 1) + return + running_table = vrfdev_attrs.get('table') + if not running_table: + ifaceobjcurr.update_config_with_status('vrf-table', 'None', 1) + return + if config_table != running_table: + ifaceobjcurr.update_config_with_status('vrf-table', + running_table, 1) + else: + ifaceobjcurr.update_config_with_status('vrf-table', + running_table, 0) + if not ifupdownflags.flags.WITHDEFAULTS: + return + if self.vrf_helper: + try: + utils.exec_command('%s verify %s %s' + %(self.vrf_helper, + ifaceobj.name, config_table)) + ifaceobjcurr.update_config_with_status('vrf-helper', + '%s create %s %s' + %(self.vrf_helper, + ifaceobj.name, + config_table), 0) + except Exception, e: + ifaceobjcurr.update_config_with_status('vrf-helper', + '%s create %s %s' + %(self.vrf_helper, + ifaceobj.name, + config_table), 1) + pass + except Exception, e: + self.log_warn(str(e)) + + def _query_check(self, ifaceobj, ifaceobjcurr): + try: + vrf_table = ifaceobj.get_attr_value_first('vrf-table') + if vrf_table: + self._iproute2_vrf_map_initialize(writetodisk=False) + self._query_check_vrf_dev(ifaceobj, ifaceobjcurr, vrf_table) + else: + vrf = ifaceobj.get_attr_value_first('vrf') + if vrf: + self._iproute2_vrf_map_initialize(writetodisk=False) + self._query_check_vrf_slave(ifaceobj, ifaceobjcurr, vrf) + except Exception, e: + self.log_warn(str(e)) + + def _query_running(self, ifaceobjrunning, ifaceobj_getfunc=None): + try: + kind = self.ipcmd.link_get_kind(ifaceobjrunning.name) + if kind == 'vrf': + vrfdev_attrs = self.ipcmd.link_get_linkinfo_attrs(ifaceobjrunning.name) + if vrfdev_attrs: + running_table = vrfdev_attrs.get('table') + if running_table: + ifaceobjrunning.update_config('vrf-table', + running_table) + elif kind == 'vrf_slave': + vrf = self.ipcmd.link_get_master(ifaceobjrunning.name) + if vrf: + ifaceobjrunning.update_config('vrf', vrf) + except Exception, e: + self.log_warn(str(e)) + + def _query(self, ifaceobj, **kwargs): + if not self.vrf_helper: + return + if (ifaceobj.link_kind & ifaceLinkKind.VRF): + ifaceobj.update_config('vrf-helper', '%s %s' %(self.vrf_helper, + ifaceobj.name)) + + _run_ops = {'pre-up' : _up, + 'post-down' : _down, + 'query-running' : _query_running, + 'query-checkcurr' : _query_check, + 'query' : _query} + + 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() + if not self.bondcmd: + self.bondcmd = bondutil() + if not self.dhclientcmd: + self.dhclientcmd = dhclient() + + def run(self, ifaceobj, operation, query_ifaceobj=None, + ifaceobj_getfunc=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 + self._init_command_handlers() + if operation == 'query-checkcurr': + op_handler(self, ifaceobj, query_ifaceobj) + else: + op_handler(self, ifaceobj, ifaceobj_getfunc=ifaceobj_getfunc) diff --git a/addons/vrrpd.py b/addons/vrrpd.py index 9791141..575e992 100644 --- a/addons/vrrpd.py +++ b/addons/vrrpd.py @@ -8,13 +8,14 @@ try: from ipaddr import IPNetwork from sets import Set from ifupdown.iface import * + from ifupdown.utils import utils from ifupdownaddons.modulebase import moduleBase from ifupdownaddons.iproute2 import iproute2 + import ifupdown.ifupdownflags as ifupdownflags import os import glob import logging import signal - import subprocess import re except ImportError, e: raise ImportError (str(e) + "- required module not found") @@ -26,12 +27,15 @@ class vrrpd(moduleBase): 'attrs': { 'vrrp-id' : {'help' : 'vrrp instance id', + 'validrange' : ['1', '4096'], 'example' : ['vrrp-id 1']}, 'vrrp-priority' : {'help': 'set vrrp priority', + 'validrange' : ['0', '255'], 'example' : ['vrrp-priority 20']}, 'vrrp-virtual-ip' : {'help': 'set vrrp virtual ip', + 'validvals' : [IPv4Address, ], 'example' : ['vrrp-virtual-ip 10.0.1.254']}}} def __init__(self, *args, **kargs): @@ -42,8 +46,8 @@ class vrrpd(moduleBase): targetpids = [] pidstr = '' try: - pidstr = subprocess.check_output(['/bin/pidof', - '%s' %cmdname]).strip('\n') + cmdl = ['/bin/pidof', cmdname] + pidstr = utils.exec_commandl(cmdl, stderr=None).strip('\n') except: pass if not pidstr: @@ -70,7 +74,7 @@ class vrrpd(moduleBase): """ 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 + if (not ifupdownflags.flags.DRYRUN and not os.path.exists('/sys/class/net/%s' %ifaceobj.name)): return @@ -94,13 +98,13 @@ class vrrpd(moduleBase): '(virtual ip not found)') return cmd = '/usr/sbin/vrrpd -n -D -i %s %s' %(ifaceobj.name, cmd) - self.exec_command(cmd) + utils.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) + utils.exec_command(cmd) def _kill_pid_from_file(self, pidfilename): if os.path.exists(pidfilename): @@ -115,7 +119,7 @@ class vrrpd(moduleBase): if not attrval: return try: - self.exec_command('/usr/sbin/ifplugd -k -i %s' %ifaceobj.name) + utils.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))) diff --git a/addons/vxlan.py b/addons/vxlan.py index a367e99..c4d05d4 100644 --- a/addons/vxlan.py +++ b/addons/vxlan.py @@ -1,10 +1,15 @@ #!/usr/bin/python from ifupdown.iface import * +from ifupdown.utils import utils from ifupdownaddons.modulebase import moduleBase from ifupdownaddons.iproute2 import iproute2 -import ifupdown.rtnetlink_api as rtnetlink_api +from ifupdownaddons.systemutils import systemUtils +from ifupdown.netlink import netlink +from ipaddr import IPv4Address +import ifupdown.ifupdownflags as ifupdownflags import logging +import os from sets import Set class vxlan(moduleBase): @@ -12,31 +17,48 @@ class vxlan(moduleBase): 'attrs' : { 'vxlan-id' : {'help' : 'vxlan id', + 'validrange' : ['0', '4096'], 'required' : True, 'example': ['vxlan-id 100']}, 'vxlan-local-tunnelip' : {'help' : 'vxlan local tunnel ip', + 'validvals' : [IPv4Address, ], 'example': ['vxlan-local-tunnelip 172.16.20.103']}, 'vxlan-svcnodeip' : {'help' : 'vxlan id', + 'validvals' : [IPv4Address, ], 'example': ['vxlan-svcnodeip 172.16.22.125']}, 'vxlan-remoteip' : {'help' : 'vxlan remote ip', + 'validvals' : [IPv4Address, ], 'example': ['vxlan-remoteip 172.16.22.127']}, 'vxlan-learning' : - {'help' : 'vxlan learning on/off', - 'example': ['vxlan-learning off'], - 'default': 'on'}, + {'help' : 'vxlan learning yes/no', + 'validvals' : ['yes', 'no', 'on', 'off'], + 'example': ['vxlan-learning no'], + 'default': 'yes'}, + 'vxlan-ageing' : + {'help' : 'vxlan aging timer', + 'validrange' : ['0', '4096'], + 'example': ['vxlan-ageing 300'], + 'default': '300'}, }} + _clagd_vxlan_anycast_ip = "" def __init__(self, *args, **kargs): moduleBase.__init__(self, *args, **kargs) self.ipcmd = None def get_dependent_ifacenames(self, ifaceobj, ifaceobjs_all=None): - if not self._is_vxlan_device(ifaceobj): - return None - ifaceobj.link_kind |= ifaceLinkKind.VXLAN + if self._is_vxlan_device(ifaceobj): + ifaceobj.link_kind |= ifaceLinkKind.VXLAN + elif ifaceobj.name == 'lo': + clagd_vxlan_list = ifaceobj.get_attr_value('clagd-vxlan-anycast-ip') + if clagd_vxlan_list: + if len(clagd_vxlan_list) != 1: + self.log_warn('%s: multiple clagd-vxlan-anycast-ip lines, using first one' + % (ifaceobj.name,)) + vxlan._clagd_vxlan_anycast_ip = clagd_vxlan_list[0] return None def _is_vxlan_device(self, ifaceobj): @@ -49,12 +71,13 @@ class vxlan(moduleBase): 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'), + svcnodeip=ifaceobj.get_attr_value_first('vxlan-svcnodeip'), remoteips=ifaceobj.get_attr_value('vxlan-remoteip'), - learning=ifaceobj.get_attr_value_first('vxlan-learning'), - ageing=ifaceobj.get_attr_value_first('vxlan-ageing')) + learning=utils.get_onoff_bool(ifaceobj.get_attr_value_first('vxlan-learning')), + ageing=ifaceobj.get_attr_value_first('vxlan-ageing'), + anycastip=self._clagd_vxlan_anycast_ip) if ifaceobj.addr_method == 'manual': - rtnetlink_api.rtnl_api.link_set(ifaceobj.name, "up") + netlink.link_set_updown(ifaceobj.name, "up") def _down(self, ifaceobj): try: @@ -62,8 +85,10 @@ class vxlan(moduleBase): except Exception, e: self.log_warn(str(e)) - def _query_check_n_update(self, ifaceobjcurr, attrname, attrval, + def _query_check_n_update(self, ifaceobj, ifaceobjcurr, attrname, attrval, running_attrval): + if not ifaceobj.get_attr_value_first(attrname): + return if running_attrval and attrval == running_attrval: ifaceobjcurr.update_config_with_status(attrname, attrval, 0) else: @@ -91,32 +116,49 @@ class vxlan(moduleBase): ifaceobjcurr.check_n_update_config_with_status_many(ifaceobj, self.get_mod_attrs(), -1) return - self._query_check_n_update(ifaceobjcurr, 'vxlan-id', + self._query_check_n_update(ifaceobj, 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')) + running_attrval = vxlanattrs.get('local') + attrval = ifaceobj.get_attr_value_first('vxlan-local-tunnelip') + if running_attrval == self._clagd_vxlan_anycast_ip: + # if local ip is anycast_ip, then let query_check to go through + attrval = self._clagd_vxlan_anycast_ip + self._query_check_n_update(ifaceobj, ifaceobjcurr, 'vxlan-local-tunnelip', + attrval, running_attrval) - self._query_check_n_update_addresses(ifaceobjcurr, 'vxlan-svcnodeip', - ifaceobj.get_attr_value('vxlan-svcnodeip'), - vxlanattrs.get('svcnode', [])) + self._query_check_n_update(ifaceobj, ifaceobjcurr, 'vxlan-svcnodeip', + ifaceobj.get_attr_value_first('vxlan-svcnodeip'), + vxlanattrs.get('svcnode')) - self._query_check_n_update_addresses(ifaceobjcurr, 'vxlan-remoteip', - ifaceobj.get_attr_value('vxlan-remoteip'), - vxlanattrs.get('remote', [])) + if not systemUtils.is_service_running(None, '/var/run/vxrd.pid'): + # vxlan-remoteip config is allowed only if vxrd is not running + self._query_check_n_update_addresses(ifaceobjcurr, 'vxlan-remoteip', + ifaceobj.get_attr_value('vxlan-remoteip'), + vxlanattrs.get('remote', [])) learning = ifaceobj.get_attr_value_first('vxlan-learning') if not learning: learning = 'on' + running_learning = vxlanattrs.get('learning') + if learning == 'yes' and running_learning == 'on': + running_learning = 'yes' + elif learning == 'no' and running_learning == 'off': + running_learning = 'no' + 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) + ageing = ifaceobj.get_attr_value_first('vxlan-ageing') + if not ageing: + ageing = self.get_mod_subattr('vxlan-ageing', 'default') + self._query_check_n_update(ifaceobj, ifaceobjcurr, 'vxlan-ageing', + ageing, vxlanattrs.get('ageing')) def _query_running(self, ifaceobjrunning): vxlanattrs = self.ipcmd.get_vxlandev_attrs(ifaceobjrunning.name) @@ -130,16 +172,19 @@ class vxlan(moduleBase): 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('remote') - if attrval: - [ifaceobjrunning.update_config('vxlan-remoteip', a) - for a in attrval] + ifaceobjrunning.update_config('vxlan-svcnode', attrval) + if not systemUtils.is_service_running(None, '/var/run/vxrd.pid'): + # vxlan-remoteip config is allowed only if vxrd is not running + attrval = vxlanattrs.get('remote') + if attrval: + [ifaceobjrunning.update_config('vxlan-remoteip', a) + for a in attrval] attrval = vxlanattrs.get('learning') if attrval and attrval == 'on': ifaceobjrunning.update_config('vxlan-learning', 'on') - + attrval = vxlanattrs.get('ageing') + if attrval: + ifaceobjrunning.update_config('vxlan-ageing', vxlanattrs.get('ageing')) _run_ops = {'pre-up' : _up, 'post-down' : _down, @@ -151,7 +196,7 @@ class vxlan(moduleBase): def _init_command_handlers(self): if not self.ipcmd: - self.ipcmd = iproute2(**self.get_flags()) + self.ipcmd = iproute2() def run(self, ifaceobj, operation, query_ifaceobj=None, **extra_args): op_handler = self._run_ops.get(operation) diff --git a/config/addons.conf b/config/addons.conf index a96b75e..73fc699 100644 --- a/config/addons.conf +++ b/config/addons.conf @@ -1,11 +1,13 @@ -pre-up,ifenslave -pre-up,clagd +pre-up,link +pre-up,bond pre-up,vlan pre-up,vxlan +pre-up,clagd pre-up,usercmds pre-up,bridge pre-up,bridgevlan pre-up,mstpctl +pre-up,vrf up,dhcp up,address up,addressvirtual @@ -13,17 +15,21 @@ up,usercmds post-up,ethtool post-up,usercmds post-up,clagd +post-up,vxrd pre-down,usercmds pre-down,ethtool +pre-down,vxrd down,dhcp down,addressvirtual down,address down,usercmds +post-down,vrf post-down,clagd post-down,mstpctl post-down,bridgevlan post-down,bridge post-down,vxlan post-down,vlan -post-down,ifenslave +post-down,bond post-down,usercmds +post-down,link diff --git a/config/ifupdown2.conf b/config/ifupdown2.conf index 82c618e..13cbbdc 100644 --- a/config/ifupdown2.conf +++ b/config/ifupdown2.conf @@ -10,6 +10,14 @@ template_engine=mako # default template lookup path during template rendering template_lookuppath=/etc/network/ifupdown2/templates +# default network configuration filepath +default_interfaces_configfile=/etc/network/interfaces + +# The -i interfacefile option is allowed by default but +# can be disabled by setting the below option to 1 to +# reduce security issues (due to the pre- and post- commands) +disable_cli_interfacesfile=0 + # Support /etc/network/if-*/ scripts addon_scripts_support=0 @@ -23,8 +31,8 @@ multiple_vlan_aware_bridge_support=0 # 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_success_str=pass +ifquery_check_error_str=fail ifquery_check_unknown_str= # @@ -42,3 +50,15 @@ delay_admin_state_change=0 # 'interfaces that were deleted'. With the below variable set to '0' # ifreload will only down 'interfaces that were deleted' ifreload_down_changed=0 + +# squash all addr config when you process the first interface +addr_config_squash=0 + +# squash iface config into one when you have multiple +# ifaces stanzas for an interface +ifaceobj_squash=0 + +# By default ifupdown2 will adjust logical devices MTU +# based on the physical interface they are running on top of. +# set this flag to 0 to disable this behaviour +adjust_logical_dev_mtu=1 diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 0000000..b296b71 --- /dev/null +++ b/debian/changelog @@ -0,0 +1,26 @@ +ifupdown2 (1.1-cl3u3) RELEASED; urgency=medium + + * Closes: CM-11214. Interface configuration parsing error when keyword vlan is the interface name. + + -- dev-support Sun, 05 Jun 2016 08:55:50 -0700 + +ifupdown2 (1.1-cl3u2) RELEASED; urgency=medium + + * Closes: CM-10478. checks for invalid address-virtual attributes + * New. Deprecated: `mstpctl-stp` attribute + * New. Deprecated: lacp parameters: bond-ad-sys-priority, bond-ad-sys-mac-addr + * New. Enabled: addon module for configuring vrf + * New. Enabled: bridge: display warning when (in vlan unware bridge) an untagged bridge is not configured + * New. Enabled: adjusting MTU for vlan devices depending on lower device mtu + * New. Enabled: introduce checks for reserved vrf table names + * New. Enabled: ifquery: new option '--with-defaults' to include default attributes + * New. Enabled: bridge: disabling ipv6 on bridge if any VXLAN port + * New. Enabled: vrf awareness in dhcp addon module + + -- dev-support Tue, 3 May 2016 14:42:42 -0700 + +ifupdown2 (1.1-cl3u1) unstable; urgency=low + + * Initial release. + + -- dev-support Thu, 20 Aug 2015 06:14:24 -0700 diff --git a/debian/clean b/debian/clean new file mode 100644 index 0000000..0723a0c --- /dev/null +++ b/debian/clean @@ -0,0 +1,4 @@ +man/ifquery.8 +man/ifreload.8 +man/ifup.8 +man/ifupdown-addons-interfaces.5 diff --git a/debian/compat b/debian/compat new file mode 100644 index 0000000..ec63514 --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +9 diff --git a/debian/control b/debian/control new file mode 100644 index 0000000..ba67c23 --- /dev/null +++ b/debian/control @@ -0,0 +1,31 @@ +Source: ifupdown2 +Section: admin +Priority: optional +Maintainer: Cumulus Networks dev support +Standards-Version: 3.9.6 +Build-Depends: python-setuptools, dh-python, python-all (>= 2.6.6-3), debhelper (>= 9~), python-docutils, dh-systemd +Homepage: https://github.com/CumulusNetworks/ifupdown2 +X-Python-Version: >= 2.6 + +Package: ifupdown2 +Architecture: all +Suggests: python-gvgen, python-mako +Replaces: ifupdown +Conflicts: ifupdown +Provides: ifupdown +Depends: ${python:Depends}, ${misc:Depends}, python-argcomplete, python-ipaddr +Description: Network Interface Management tool similar to ifupdown + ifupdown2 is ifupdown re-written in Python. It replaces ifupdown and provides + the same user interface as ifupdown for network interface configuration. + Like ifupdown, ifupdown2 is a high level tool to configure (or, respectively + deconfigure) network interfaces based on interface definitions in + /etc/network/interfaces. It is capable of detecting network interface + dependencies and comes with several new features which are available as + new command options to ifup/ifdown/ifquery commands. It also comes with a new + command ifreload to reload interface configuration with minimum + disruption. Most commands are also capable of input and output in JSON format. + It is backward compatible with ifupdown /etc/network/interfaces format and + supports newer simplified format. It also supports interface templates with + python-mako for large scale interface deployments. See + /usr/share/doc/ifupdown2/README.rst for details about ifupdown2. Examples + are available under /usr/share/doc/ifupdown2/examples. diff --git a/debian/copyright b/debian/copyright index f560d80..2e6a979 100644 --- a/debian/copyright +++ b/debian/copyright @@ -1,5 +1,6 @@ Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: ifupdown2 +<<<<<<< HEAD Source: http://www.cumulusnetworks.com Files: * @@ -8,6 +9,19 @@ License: GPL-2 Files: debian/* Copyright: 2013 Cumulus Networks +======= +Upstream-Contact: Cumulus Networks dev support +Source: http://www.cumulusnetworks.com + +Files: * +Copyright: 2014 Cumulus Networks +License: GPL-2 + +Files: debian/* +Copyright: 2014 Cumulus Networks +License: GPL-2 + +>>>>>>> cumulus/dev License: GPL-2 This package is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -23,8 +37,11 @@ License: GPL-2 . On Debian systems, the complete text of the GNU General Public License version 2 can be found in "/usr/share/common-licenses/GPL-2". +<<<<<<< HEAD # Please also look if there are files or directories which have a # different copyright/license attached and list them here. # Please avoid to pick license terms that are more restrictive than the # packaged work, as it may make Debian's contributions unacceptable upstream. +======= +>>>>>>> cumulus/dev diff --git a/debian/ifupdown2.bash-completion b/debian/ifupdown2.bash-completion new file mode 100644 index 0000000..cf6dbfb --- /dev/null +++ b/debian/ifupdown2.bash-completion @@ -0,0 +1 @@ +completion/ifup diff --git a/debian/ifupdown2.dirs b/debian/ifupdown2.dirs new file mode 100644 index 0000000..675c5f6 --- /dev/null +++ b/debian/ifupdown2.dirs @@ -0,0 +1,3 @@ +/etc/network/interfaces.d/ +/var/lib/ifupdown2/policy.d/ +/var/lib/ifupdown2/hooks/ diff --git a/debian/ifupdown2.docs b/debian/ifupdown2.docs new file mode 100644 index 0000000..5c86fa8 --- /dev/null +++ b/debian/ifupdown2.docs @@ -0,0 +1 @@ +docs/README.rst diff --git a/debian/ifupdown2.examples b/debian/ifupdown2.examples new file mode 100644 index 0000000..684a743 --- /dev/null +++ b/debian/ifupdown2.examples @@ -0,0 +1 @@ +docs/examples/* diff --git a/debian/ifupdown2.install b/debian/ifupdown2.install new file mode 100644 index 0000000..446b2ac --- /dev/null +++ b/debian/ifupdown2.install @@ -0,0 +1,3 @@ +sbin/ifupdown2 /usr/share/ifupdown2/ +sbin/start-networking /usr/share/ifupdown2/sbin/ +debian/networking /etc/default/ diff --git a/debian/ifupdown2.links b/debian/ifupdown2.links new file mode 100644 index 0000000..177c9d5 --- /dev/null +++ b/debian/ifupdown2.links @@ -0,0 +1,6 @@ +usr/share/ifupdown2/ifupdown2 sbin/ifup +usr/share/ifupdown2/ifupdown2 sbin/ifdown +usr/share/ifupdown2/ifupdown2 sbin/ifquery +usr/share/ifupdown2/ifupdown2 sbin/ifreload +usr/share/man/man8/ifup.8.gz usr/share/man/man8/ifdown.8.gz + diff --git a/debian/ifupdown2.manpages b/debian/ifupdown2.manpages new file mode 100644 index 0000000..b7d0fab --- /dev/null +++ b/debian/ifupdown2.manpages @@ -0,0 +1,5 @@ +man/ifup.8 +man/ifquery.8 +man/ifreload.8 +man/ifupdown-addons-interfaces.5 +man/interfaces.5 diff --git a/debian/ifupdown2.networking.service b/debian/ifupdown2.networking.service new file mode 100644 index 0000000..ecf5b91 --- /dev/null +++ b/debian/ifupdown2.networking.service @@ -0,0 +1,15 @@ +[Unit] +Description=ifupdown2 networking initialization +Documentation=man:interfaces(5) man:ifup(8) man:ifdown(8) + +[Service] +Type=oneshot +RemainAfterExit=yes +SyslogIdentifier=networking +TimeoutStopSec=30s +ExecStart=/usr/share/ifupdown2/sbin/start-networking start +ExecStop=/usr/share/ifupdown2/sbin/start-networking stop +ExecReload=/usr/share/ifupdown2/sbin/start-networking reload + +[Install] +WantedBy=basic.target network.target diff --git a/debian/ifupdown2.postinst b/debian/ifupdown2.postinst new file mode 100644 index 0000000..e1df311 --- /dev/null +++ b/debian/ifupdown2.postinst @@ -0,0 +1,76 @@ +#!/bin/sh +# postinst script for ifupdown2 +# +# see: dh_installdeb(1) + +set -e + +# summary of how this script can be called: +# * `configure' +# * `abort-upgrade' +# * `abort-remove' `in-favour' +# +# * `abort-remove' +# * `abort-deconfigure' `in-favour' +# `removing' +# +# for details, see http://www.debian.org/doc/debian-policy/ or +# the debian-policy package + +process_udev() +{ + # override default udev bridge and hotplug rules because they interfere with + # networking init script + udev_user_rulesdir=/etc/udev/rules.d/ + udev_sys_rulesdir=/lib/udev/rules.d/ + if [ -e $udev_user_rulesdir ]; then + udev_ifupdown2_overrides="80-networking.rules + 60-bridge-network-interface.rules" + for u in ${udev_ifupdown2_overrides} + do + if [ -e ${udev_sys_rulesdir}/$u -a ! -e ${udev_user_rulesdir}/$u ]; then + (cd ${udev_user_rulesdir} && ln -sf /dev/null $u) + fi + done + fi +} + +MYNAME="${0##*/}" + +report() { echo "${MYNAME}: $*" ; } +report_warn() { report "Warning: $*" >&2 ; } +report_err() { report "Error: $*" >&2 ; } + +case "$1" in + configure) + + # Generic stuff done on all configurations + if [ -f /etc/network/interfaces ] ; then + if ! grep -q "^[[:space:]]*iface[[:space:]]\+lo0\?[[:space:]]\+inet[[:space:]]\+loopback\>" /etc/network/interfaces ; then + report_warn "No 'iface lo' definition found in /etc/network/interfaces" + fi + + if ! grep -q "^[[:space:]]*\(allow-\|\)auto[[:space:]]\+\(.*[[:space:]]\+\|\)lo0\?\([[:space:]]\+\|$\)" /etc/network/interfaces ; then + report_warn "No 'auto lo' statement found in /etc/network/interfaces" + fi + else # ! -f /etc/network/interfaces + if [ -z "$2" ]; then + echo "Creating /etc/network/interfaces." + echo "# interfaces(5) file used by ifup(8) and ifdown(8)" > /etc/network/interfaces + echo "auto lo" >> /etc/network/interfaces + echo "iface lo inet loopback" >> /etc/network/interfaces + else + report_warn "/etc/network/interfaces does not exist" + fi + fi + + process_udev + ;; + + purge) + ;; +esac + +#DEBHELPER# + +exit 0 diff --git a/debian/ifupdown2.postrm b/debian/ifupdown2.postrm new file mode 100644 index 0000000..e7bb6a2 --- /dev/null +++ b/debian/ifupdown2.postrm @@ -0,0 +1,69 @@ +#!/bin/sh +# postrm script for ifupdown2 +# +# see: dh_installdeb(1) + +set -e + +# summary of how this script can be called: +# * `remove' +# * `purge' +# * `upgrade' +# * `failed-upgrade' +# * `abort-install' +# * `abort-install' +# * `abort-upgrade' +# * `disappear' +# +# for details, see http://www.debian.org/doc/debian-policy/ or +# the debian-policy package + +process_udev() +{ + if [ -e /etc/udev/rules.d/80-networking.rules ]; then + udevlink=$(readlink /etc/udev/rules.d/80-networking.rules 2>/dev/null || true) + [ -n "$udevlink" -a "$udevlink" = "/dev/null" ] && rm -f /etc/udev/rules.d/80-networking.rules + fi + + if [ -e /etc/udev/rules.d/60-bridge-network-interface.rules ]; then + udevlink=$(readlink /etc/udev/rules.d/60-bridge-network-interface.rules 2>/dev/null || true) + [ -n "$udevlink" -a "$udevlink" = "/dev/null" ] && rm -f /etc/udev/rules.d/60-bridge-network-interface.rules + fi +} + +postrm_remove() +{ + process_udev +} + +# Note: We don't remove /etc/network/interfaces +postrm_purge() +{ + rm -f /run/network/ifstatenew +} + +case "$1" in + purge) + postrm_purge + ;; + + remove) + postrm_remove + ;; + + + upgrade|disappear|failed-upgrade|abort-install|abort-upgrade) + ;; + + *) + echo "postrm called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +# dh_installdeb will replace this with shell code automatically +# generated by other debhelper scripts. + +#DEBHELPER# + +exit 0 diff --git a/debian/ifupdown2.preinst b/debian/ifupdown2.preinst new file mode 100644 index 0000000..67eb14a --- /dev/null +++ b/debian/ifupdown2.preinst @@ -0,0 +1,13 @@ +#! /bin/sh +set -e +case "$1" in + install|upgrade) + # workaround 3.0.0 internal install error. This can be removed in a + # few weeks. + if [ -f /etc/default/networking/networking.default ]; then + dpkg-maintscript-helper rm_conffile /etc/default/networking/networking.default 1.1 -- $@ + rm -rf /etc/default/networking + fi + ;; +esac + diff --git a/debian/networking b/debian/networking new file mode 100644 index 0000000..5856199 --- /dev/null +++ b/debian/networking @@ -0,0 +1,23 @@ +# +# +# Parameters for the networking service +# +# + +# 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= + +# Set to 'yes' if you want to skip ifdown during system reboot +# and shutdown. This is of interest in large scale interface +# deployments where you dont want to wait for interface +# deconfiguration to speed up shutdown/reboot +SKIP_DOWN_AT_SYSRESET="yes" diff --git a/debian/rules b/debian/rules new file mode 100755 index 0000000..db0fef3 --- /dev/null +++ b/debian/rules @@ -0,0 +1,21 @@ +#!/usr/bin/make -f + +#export DH_VERBOSE=1 +export PYBUILD_NAME=ifupdown2 +export PYBUILD_INSTALL_ARGS=--install-lib=/usr/share/ifupdown2 --install-scripts=/usr/share/ifupdown2 + +%: + dh $@ --with python2 --with systemd --buildsystem=pybuild + +override_dh_installman: + ./scripts/genmanpages.sh ./man.rst ./man + dh_installman + +override_dh_systemd_start: + dh_systemd_start --name=networking --no-start + +override_dh_systemd_enable: + dh_systemd_enable --name=networking + +override_dh_compress: + dh_compress -X.py diff --git a/debian/source/format b/debian/source/format new file mode 100644 index 0000000..af745b3 --- /dev/null +++ b/debian/source/format @@ -0,0 +1 @@ +3.0 (git) diff --git a/docs/README.rst b/docs/README.rst new file mode 100644 index 0000000..8ab8252 --- /dev/null +++ b/docs/README.rst @@ -0,0 +1,78 @@ +python-ifupdown2 +================ + +This package is a replacement for the debian ifupdown package. +It is ifupdown re-written in python. It maintains the original ifupdown +pluggable architecture and extends it further. + +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/. +To make the transition smoother, a python module under +/usr/share/ifupdownmodules will override a script by the same name under +/etc/network/. + +It publishes an interface object which is passed to all loadble python +modules. For more details on adding a addon module, see the section on +adding python modules. + + +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. + +python-ifupdown2 expects a few things from the pluggable modules: +- the module should implement a class by the same name +- the interface object (class iface) and the operation to be performed is + passed to the modules +- the python addon class should 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 + 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). + +Example: Address handling module /usr/share/ifupdownaddons/address.py + + +build +===== +- get source + +- install build dependencies: + apt-get install python-stdeb + apt-get install python-docutils + +- cd && ./build.sh + + (generates python-ifupdown2-.deb) + +install +======= + +- remove existing ifupdown package + dpkg -r ifupdown + +- install python-ifupdown2 using `dpkg -i` + +- 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/docs/examples/generate_interfaces.py b/docs/examples/generate_interfaces.py index bfb2d88..d4b2d65 100755 --- a/docs/examples/generate_interfaces.py +++ b/docs/examples/generate_interfaces.py @@ -3,6 +3,10 @@ import argparse import sys import subprocess +<<<<<<< HEAD +======= +import os +>>>>>>> cumulus/dev """ This script prints to stdout /etc/network/interfaces entries for requested interfaces. @@ -30,6 +34,7 @@ import subprocess """ +<<<<<<< HEAD def get_swp_interfaces(): porttab_path = '/var/lib/cumulus/porttab' ports = [] @@ -43,6 +48,49 @@ def get_swp_interfaces(): ports.append(line.split()[0]) except ValueError: continue +======= +def get_pci_interfaces(): + ports = [] + FNULL = open(os.devnull, 'w') + try: + cmd = '(ip -o link show | grep -v "@" | cut -d" " -f2 | sed \'s/:$//\')' + output = subprocess.check_output(cmd, shell=True).split() + for interface in output: + cmd = 'udevadm info -a -p /sys/class/net/%s | grep \'SUBSYSTEMS=="pci"\'' % interface + try: + subprocess.check_call(cmd, shell=True, stdout=FNULL) + ports.append(interface) + except: + pass + except: + pass + finally: + FNULL.close() + return ports + +def get_swp_interfaces(): + porttab_path = '/var/lib/cumulus/porttab' + ports = [] + try: + with open(porttab_path, 'r') as f: + for line in f.readlines(): + line = line.strip() + if '#' in line: + continue + try: + ports.append(line.split()[0]) + except ValueError: + continue + except: + try: + ports = get_pci_interfaces() + except Exception as e: + print 'Error: Unsupported script: %s' % str(e) + exit(1) + if not ports: + print 'Error: No ports found in %s' % porttab_path + exit(1) +>>>>>>> cumulus/dev return ports def print_swp_defaults_header(): @@ -138,9 +186,12 @@ if args.bridgedefault and args.mergefile: exit(1) swp_intfs = get_swp_interfaces() +<<<<<<< HEAD if not swp_intfs: print 'error: no ports found' exit(1) +======= +>>>>>>> cumulus/dev if args.swpdefaults: interfaces_print_swp_defaults(swp_intfs) diff --git a/docs/examples/interfaces b/docs/examples/interfaces index df05fc1..605cd7a 100644 --- a/docs/examples/interfaces +++ b/docs/examples/interfaces @@ -21,16 +21,27 @@ iface swp30 alias "test network" link-duplex full link-speed 1000 +<<<<<<< HEAD link-autoneg off +======= + link-autoneg no +>>>>>>> cumulus/dev # bond interface auto bond3 iface bond3 inet static +<<<<<<< HEAD address 100.0.0.4/16 bond-slaves swp1 swp2 bond-mode 802.3ad bond-miimon 100 bond-use-carrier 1 +======= + bond-slaves swp1 swp2 + bond-mode 802.3ad + bond-miimon 100 + bond-use-carrier yes +>>>>>>> cumulus/dev bond-lacp-rate 1 bond-min-links 1 bond-xmit_hash_policy layer3+4 @@ -38,16 +49,27 @@ iface bond3 inet static # bond interface auto bond4 iface bond4 inet static +<<<<<<< HEAD address 100.0.0.6/16 bond-slaves swp3 swp4 bond-mode 802.3ad bond-miimon 100 bond-use-carrier 1 +======= + bond-slaves swp3 swp4 + bond-mode 802.3ad + bond-miimon 100 + bond-use-carrier yes +>>>>>>> cumulus/dev bond-lacp-rate 1 bond-min-links 1 bond-xmit_hash_policy layer3+4 +<<<<<<< HEAD # bond interface +======= +# bridge interface +>>>>>>> cumulus/dev auto br0 iface br0 address 12.0.0.4/24 @@ -59,11 +81,17 @@ iface br0 # vlan interface on bond auto bond3.2000 iface bond3.2000 inet static +<<<<<<< HEAD address 100.1.0.4/16 auto bond4.2000 iface bond4.2000 inet static address 100.1.0.6/16 +======= + +auto bond4.2000 +iface bond4.2000 inet static +>>>>>>> cumulus/dev auto br2000 iface br2000 inet6 static diff --git a/docs/examples/interfaces_bridge_igmp_mstp b/docs/examples/interfaces_bridge_igmp_mstp index f4916f4..4c2fb20 100644 --- a/docs/examples/interfaces_bridge_igmp_mstp +++ b/docs/examples/interfaces_bridge_igmp_mstp @@ -31,11 +31,19 @@ iface br-300 inet static mstpctl-hello 2 mstpctl-portnetwork swp13.300=no bridge-mclmc 3 +<<<<<<< HEAD bridge-mcrouter 0 bridge-mcsnoop 1 bridge-mcsqc 3 bridge-mcqifaddr 1 bridge-mcquerier 1 +======= + bridge-mcrouter no + bridge-mcsnoop yes + bridge-mcsqc 3 + bridge-mcqifaddr yes + bridge-mcquerier yes +>>>>>>> cumulus/dev bridge-hashel 3 bridge-hashmax 4 bridge-mclmi 3 diff --git a/docs/examples/mgmt-vrf b/docs/examples/mgmt-vrf new file mode 100644 index 0000000..4706823 --- /dev/null +++ b/docs/examples/mgmt-vrf @@ -0,0 +1,16 @@ + +# Example config for management VRF +# - 'vrf-default-route no' tells ifupdown2 not to install +# the default unreachable route (dhclient will add the +# default route) + +auto eth0 +iface eth0 inet dhcp + vrf mgmt + +auto mgmt +iface mgmt + address 127.0.0.1/8 + vrf-table auto + vrf-default-route no + diff --git a/docs/examples/vlan_aware_bridges/interfaces.with_bonds b/docs/examples/vlan_aware_bridges/interfaces.with_bonds index f3425dd..dfd4b63 100644 --- a/docs/examples/vlan_aware_bridges/interfaces.with_bonds +++ b/docs/examples/vlan_aware_bridges/interfaces.with_bonds @@ -23,7 +23,11 @@ iface uplink1 bond-slaves swp32 bond-mode 802.3ad bond-miimon 100 +<<<<<<< HEAD bond-use-carrier 1 +======= + bond-use-carrier yes +>>>>>>> cumulus/dev bond-lacp-rate 1 bond-min-links 1 bond-xmit-hash-policy layer2 @@ -35,7 +39,11 @@ iface peerlink bond-slaves swp30 swp31 bond-mode 802.3ad bond-miimon 100 +<<<<<<< HEAD bond-use-carrier 1 +======= + bond-use-carrier yes +>>>>>>> cumulus/dev bond-lacp-rate 1 bond-min-links 1 bond-xmit-hash-policy layer3+4 @@ -47,7 +55,11 @@ iface downlink bond-slaves swp1 bond-mode 802.3ad bond-miimon 100 +<<<<<<< HEAD bond-use-carrier 1 +======= + bond-use-carrier yes +>>>>>>> cumulus/dev bond-lacp-rate 1 bond-min-links 1 bond-xmit-hash-policy layer3+4 diff --git a/docs/examples/vlan_aware_bridges/interfaces.with_clag b/docs/examples/vlan_aware_bridges/interfaces.with_clag index 8cdccc9..fabc245 100644 --- a/docs/examples/vlan_aware_bridges/interfaces.with_clag +++ b/docs/examples/vlan_aware_bridges/interfaces.with_clag @@ -25,7 +25,11 @@ iface spine-bond bond-slaves glob swp19-22 bond-mode 802.3ad bond-miimon 100 +<<<<<<< HEAD bond-use-carrier 1 +======= + bond-use-carrier yes +>>>>>>> cumulus/dev bond-lacp-rate 1 bond-min-links 1 bond-xmit-hash-policy layer3+4 @@ -38,7 +42,11 @@ iface peer-bond bond-slaves glob swp23-24 bond-mode 802.3ad bond-miimon 100 +<<<<<<< HEAD bond-use-carrier 1 +======= + bond-use-carrier yes +>>>>>>> cumulus/dev bond-lacp-rate 1 bond-min-links 1 bond-xmit-hash-policy layer3+4 @@ -61,7 +69,11 @@ iface host-bond-01 bond-slaves swp1 bond-mode 802.3ad bond-miimon 100 +<<<<<<< HEAD bond-use-carrier 1 +======= + bond-use-carrier yes +>>>>>>> cumulus/dev bond-lacp-rate 1 bond-min-links 1 bond-xmit-hash-policy layer3+4 @@ -72,7 +84,11 @@ iface host-bond-02 bond-slaves swp2 bond-mode 802.3ad bond-miimon 100 +<<<<<<< HEAD bond-use-carrier 1 +======= + bond-use-carrier yes +>>>>>>> cumulus/dev bond-lacp-rate 1 bond-min-links 1 bond-xmit-hash-policy layer3+4 diff --git a/docs/source/addonsapiref.rst b/docs/source/addonsapiref.rst index 0954d27..9b114fd 100644 --- a/docs/source/addonsapiref.rst +++ b/docs/source/addonsapiref.rst @@ -32,12 +32,21 @@ ethtool .. autoclass:: ethtool +<<<<<<< HEAD ifenslave ========= .. automodule:: ifenslave .. autoclass:: ifenslave +======= +bond +==== + +.. automodule:: bond + +.. autoclass:: bond +>>>>>>> cumulus/dev mstpctl ======= diff --git a/docs/source/addonshelperapiref.rst b/docs/source/addonshelperapiref.rst index 01d9a41..110d80a 100644 --- a/docs/source/addonshelperapiref.rst +++ b/docs/source/addonshelperapiref.rst @@ -15,15 +15,26 @@ Helper module to work with bridgeutil commands .. autoclass:: brctl +<<<<<<< HEAD ifenslaveutil ============= +======= +bondutil +======== +>>>>>>> cumulus/dev Helper module to interact with linux api to create bonds. Currently this is via sysfs. +<<<<<<< HEAD .. automodule:: ifenslaveutil .. autoclass:: ifenslaveutil +======= +.. automodule:: bondutil + +.. autoclass:: bondutil +>>>>>>> cumulus/dev dhclient ======== diff --git a/docs/source/userguide.rst b/docs/source/userguide.rst index c06e9d2..818ce62 100644 --- a/docs/source/userguide.rst +++ b/docs/source/userguide.rst @@ -66,7 +66,11 @@ Man Pages Configuration Files =================== +<<<<<<< HEAD * /etc/network/interfaces +======= +* config file defined in ifupdown2.conf (default /etc/network/interfaces) +>>>>>>> cumulus/dev ifupdown Built-in Interfaces @@ -109,7 +113,11 @@ following example configuration:: bond-slaves swp29 swp30 bond-mode 802.3ad bond-miimon 100 +<<<<<<< HEAD bond-use-carrier 1 +======= + bond-use-carrier yes +>>>>>>> cumulus/dev bond-lacp-rate 1 bond-min-links 1 bond-xmit-hash-policy layer3+4 @@ -120,7 +128,11 @@ following example configuration:: bond-slaves swp31 swp32 bond-mode 802.3ad bond-miimon 100 +<<<<<<< HEAD bond-use-carrier 1 +======= + bond-use-carrier yes +>>>>>>> cumulus/dev bond-lacp-rate 1 bond-min-links 1 bond-xmit-hash-policy layer3+4 @@ -298,7 +310,11 @@ The contents of the sourced file used above are:: bond-slaves swp25 swp26 bond-mode 802.3ad bond-miimon 100 +<<<<<<< HEAD bond-use-carrier 1 +======= + bond-use-carrier yes +>>>>>>> cumulus/dev bond-lacp-rate 1 bond-min-links 1 bond-xmit-hash-policy layer3+4 @@ -317,6 +333,13 @@ bridge ports and bond slaves:: iface br1 bridge-ports glob swp7-9.100 swp11.100 glob swp15-18.100 +<<<<<<< HEAD +======= + auto br2 + iface br2 + bridge-ports glob swp[1-6]s[0-3].100 + +>>>>>>> cumulus/dev Using Templates =============== @@ -359,7 +382,11 @@ file, run:: bond-slaves swp25 swp26 bond-mode 802.3ad bond-miimon 100 +<<<<<<< HEAD bond-use-carrier 1 +======= + bond-use-carrier yes +>>>>>>> cumulus/dev bond-lacp-rate 1 bond-min-links 1 bond-xmit-hash-policy layer3+4 @@ -375,7 +402,11 @@ does not match:: iface bond0 bond-mode 802.3ad (✓) bond-miimon 100 (✓) +<<<<<<< HEAD bond-use-carrier 1 (✓) +======= + bond-use-carrier yes (✓) +>>>>>>> cumulus/dev bond-lacp-rate 1 (✓) bond-min-links 1 (✓) bond-xmit-hash-policy layer3+4 (✓) @@ -413,10 +444,17 @@ the ``interfaces`` file. For complete syntax on the ``interfaces`` file, see { "auto": true, "config": { +<<<<<<< HEAD "bond-use-carrier": "1", "bond-xmit-hash-policy": "layer3+4", "bond-miimon": "100", "bond-lacp-rate": "1", +======= + "bond-use-carrier": "yes", + "bond-xmit-hash-policy": "layer3+4", + "bond-miimon": "100", + "bond-lacp-rate": "1", +>>>>>>> cumulus/dev "bond-min-links": "1", "bond-slaves": "swp25 swp26", "bond-mode": "802.3ad", diff --git a/ifupdown/graph.py b/ifupdown/graph.py index 49b1348..e1729d0 100644 --- a/ifupdown/graph.py +++ b/ifupdown/graph.py @@ -18,9 +18,7 @@ except ImportError, e: class graph(): """ graph functions to sort and print interface graph """ - def __init__(self): - self.logger = logging.getLogger('ifupdown.' + - self.__class__.__name__) + logger = logging.getLogger('ifupdown.graph') @classmethod def topological_sort_graphs_all(cls, dependency_graphs, indegrees_arg): @@ -51,7 +49,12 @@ class graph(): continue for y in dlist: - indegrees[y] = indegrees.get(y) - 1 + try: + indegrees[y] = indegrees.get(y) - 1 + except: + cls.logger.debug('topological_sort_graphs_all: did not find %s' %y) + indegrees[y] = 0 + pass if indegrees.get(y) == 0: Q.append(y) diff --git a/ifupdown/iface.py b/ifupdown/iface.py index 52e8476..3c70761 100644 --- a/ifupdown/iface.py +++ b/ifupdown/iface.py @@ -17,11 +17,18 @@ from collections import OrderedDict import logging import json -class ifaceType(): - UNKNOWN = 0x0 - IFACE = 0x1 - BRIDGE_VLAN = 0x2 +class ifaceStatusUserStrs(): + """ This class declares strings user can see during an ifquery --check + for example. These strings can be overridden by user defined strings from + config file """ + SUCCESS = "success", + FAILURE = "error", + UNKNOWN = "unknown" +class ifaceType(): + UNKNOWN = 0x00 + IFACE = 0x01 + BRIDGE_VLAN = 0x10 class ifaceRole(): """ ifaceRole is used to classify the ifaceobj.role of @@ -29,9 +36,9 @@ class ifaceRole(): with bond-slaves or bridge-ports. A bond in a bridge is both a master and slave (0x3) """ - UNKNOWN = 0x0 - SLAVE = 0x1 - MASTER = 0x2 + UNKNOWN = 0x00 + SLAVE = 0x01 + MASTER = 0x10 class ifaceLinkKind(): """ ifaceLlinkKind is used to identify interfaces @@ -39,11 +46,52 @@ class ifaceLinkKind(): bond have an ifaceobj.role attribute of SLAVE and the bridge or bond itself has ifaceobj.role of MASTER. """ - UNKNOWN = 0x0 - BRIDGE = 0x1 - BOND = 0x2 - VLAN = 0x4 - VXLAN = 0x8 + UNKNOWN = 0x000000 + BRIDGE = 0x000001 + BOND = 0x000010 + VLAN = 0x000100 + VXLAN = 0x001000 + VRF = 0x010000 + +class ifaceLinkPrivFlags(): + """ This corresponds to kernel netdev->priv_flags + and can be BRIDGE_PORT, BOND_SLAVE etc """ + UNKNOWN = 0x00000 + BRIDGE_PORT = 0x00001 + BOND_SLAVE = 0x00010 + VRF_SLAVE = 0x00100 + BRIDGE_VLAN_AWARE = 0x01000 + BRIDGE_VXLAN = 0x10000 + + @classmethod + def get_str(cls, flag): + if flag == cls.UNKNOWN: + return 'unknown' + elif flag == cls.BRIDGE_PORT: + return 'bridge port' + elif flag == cls.BOND_SLAVE: + return 'bond slave' + elif flag == cls.VRF_SLAVE: + return 'vrf slave' + elif flag == cls.BRIDGE_VLAN_AWARE: + return 'vlan aware bridge' + elif flag == cls.BRIDGE_VXLAN: + return 'vxlan bridge' + + @classmethod + def get_all_str(cls, flags): + str = '' + if flags & cls.BRIDGE_PORT: + str += 'bridgeport ' + if flags & cls.BOND_SLAVE: + str += 'bondslave ' + if flags & cls.VRF_SLAVE: + str += 'vrfslave ' + if flags & cls.BRIDGE_VLAN_AWARE: + str += 'vlanawarebridge ' + if flags & cls.BRIDGE_VXLAN: + str += 'vxlanbridge ' + return str class ifaceLinkType(): LINK_UNKNOWN = 0x0 @@ -86,8 +134,9 @@ class ifaceStatus(): UNKNOWN = 0x1 SUCCESS = 0x2 - ERROR = 0x3 - NOTFOUND = 0x4 + WARNING = 0x3 + ERROR = 0x4 + NOTFOUND = 0x5 @classmethod def to_str(cls, state): @@ -174,20 +223,65 @@ class ifaceState(): class ifaceJsonEncoder(json.JSONEncoder): def default(self, o): retconfig = {} - if o.config: + retifacedict = OrderedDict([]) + if o.config: retconfig = dict((k, (v[0] if len(v) == 1 else v)) for k,v in o.config.items()) - return OrderedDict({'name' : o.name, - 'addr_method' : o.addr_method, - 'addr_family' : o.addr_family, - 'auto' : o.auto, - 'config' : retconfig}) + retifacedict['name'] = o.name + if o.addr_method: + retifacedict['addr_method'] = o.addr_method + if o.addr_family: + retifacedict['addr_family'] = o.addr_family + retifacedict['auto'] = o.auto + retifacedict['config'] = retconfig + + return retifacedict + +class ifaceJsonEncoderWithStatus(json.JSONEncoder): + def default(self, o): + retconfig = {} + retconfig_status = {} + retifacedict = OrderedDict([]) + if o.config: + for k,v in o.config.items(): + idx = 0 + vitem_status = [] + for vitem in v: + s = o.get_config_attr_status(k, idx) + if s == -1: + status_str = ifaceStatusUserStrs.UNKNOWN + elif s == 1: + status_str = ifaceStatusUserStrs.ERROR + elif s == 0: + status_str = ifaceStatusUserStrs.SUCCESS + vitem_status.append('%s' %status_str) + idx += 1 + retconfig[k] = v[0] if len(v) == 1 else v + retconfig_status[k] = vitem_status[0] if len(vitem_status) == 1 else vitem_status + + if (o.status == ifaceStatus.NOTFOUND or + o.status == ifaceStatus.ERROR): + status = ifaceStatusUserStrs.ERROR + else: + status = ifaceStatusUserStrs.SUCCESS + + retifacedict['name'] = o.name + if o.addr_method: + retifacedict['addr_method'] = o.addr_method + if o.addr_family: + retifacedict['addr_family'] = o.addr_family + retifacedict['auto'] = o.auto + retifacedict['config'] = retconfig + retifacedict['config_status'] = retconfig_status + retifacedict['status'] = status + + return retifacedict class ifaceJsonDecoder(): @classmethod def json_to_ifaceobj(cls, ifaceattrdict): ifaceattrdict['config'] = OrderedDict([(k, (v if isinstance(v, list) - else [v])) + else [v.strip()])) for k,v in ifaceattrdict.get('config', OrderedDict()).items()]) return iface(attrsdict=ifaceattrdict) @@ -238,10 +332,13 @@ class iface(): """ # flag to indicate that the object was created from pickled state - _PICKLED = 0x00000001 - HAS_SIBLINGS = 0x00000010 + # XXX: Move these flags into a separate iface flags class + _PICKLED = 0x00000001 + HAS_SIBLINGS = 0x00000010 IFACERANGE_ENTRY = 0x00000100 IFACERANGE_START = 0x00001000 + OLDEST_SIBLING = 0x00010000 + YOUNGEST_SIBLING = 0x00100000 version = '0.1' @@ -257,7 +354,7 @@ class iface(): """iface status str (string representing the status) """ self.flags = 0x0 """iface flags """ - self.priv_flags = 0x0 + self.priv_flags = None """iface module flags dictionary with module name: flags""" self.module_flags = {} """iface priv flags. can be used by the external object manager """ @@ -282,10 +379,12 @@ class iface(): self.realname = None self.link_type = ifaceLinkType.LINK_UNKNOWN self.link_kind = ifaceLinkKind.UNKNOWN + self.link_privflags = ifaceLinkPrivFlags.UNKNOWN # The below attribute is used to disambiguate between various # types of dependencies self.dependency_type = ifaceDependencyType.UNKNOWN + self.blacklisted = False def _set_attrs_from_dict(self, attrdict): self.auto = attrdict.get('auto', False) @@ -322,7 +421,13 @@ class iface(): def set_state_n_status(self, state, status): """ sets state and status of an interface """ self.state = state - self.status = status + if status > self.status: + self.status = status + + def set_status(self, status): + """ sets status of an interface """ + if status > self.status: + self.status = status def set_flag(self, flag): self.flags |= flag @@ -338,6 +443,14 @@ class iface(): else: self.upperifaces = [upperifacename] + def add_to_lowerifaces(self, lowerifacename): + """ add to the list of lowerifaces """ + if self.lowerifaces: + if lowerifacename not in self.lowerifaces: + self.lowerifaces.append(lowerifacename) + else: + self.lowerifaces = [lowerifacename] + def get_attr_value(self, attr_name): """ add to the list of upperifaces """ return self.config.get(attr_name) @@ -457,6 +570,16 @@ class iface(): if v != dstiface.config.get(k)): return False return True + def squash(self, newifaceobj): + """ This squashes the iface object """ + for attrname, attrlist in newifaceobj.config.iteritems(): + # if allready present add it to the list + # else add it to the end of the dictionary + # We need to maintain order. + if self.config.get(attrname): + self.config[attrname].extend(attrlist) + else: + self.config.update([(attrname, attrlist)]) def __getstate__(self): odict = self.__dict__.copy() @@ -474,8 +597,10 @@ class iface(): del odict['env'] del odict['link_type'] del odict['link_kind'] + del odict['link_privflags'] del odict['role'] del odict['dependency_type'] + del odict['blacklisted'] return odict def __setstate__(self, dict): @@ -490,13 +615,15 @@ class iface(): self.linkstate = None self.env = None self.role = ifaceRole.UNKNOWN - self.priv_flags = 0 + self.priv_flags = None self.module_flags = {} self.raw_config = [] self.flags |= self._PICKLED self.link_type = ifaceLinkType.LINK_NA self.link_kind = ifaceLinkKind.UNKNOWN + self.link_privflags = ifaceLinkPrivFlags.UNKNOWN self.dependency_type = ifaceDependencyType.UNKNOWN + self.blacklisted = False def dump_raw(self, logger): indent = ' ' @@ -523,15 +650,19 @@ class iface(): else: logger.info(indent + 'lowerdevs: None') + d = self.upperifaces + if d: + logger.info(indent + 'upperdevs: %s' %str(d)) + else: + logger.info(indent + 'upperdevs: None') + logger.info(indent + 'config: ') config = self.config if config: logger.info(indent + indent + str(config)) logger.info('}') - def dump_pretty(self, with_status=False, - successstr='success', errorstr='error', - unknownstr='unknown', use_realname=False): + def dump_pretty(self, with_status=False, use_realname=False): indent = '\t' outbuf = '' if use_realname and self.realname: @@ -555,9 +686,9 @@ class iface(): self.status == ifaceStatus.NOTFOUND): if self.status_str: ifaceline += ' (%s)' %self.status_str - status_str = errorstr + status_str = '[%s]' %ifaceStatusUserStrs.ERROR elif self.status == ifaceStatus.SUCCESS: - status_str = successstr + status_str = '[%s]' %ifaceStatusUserStrs.SUCCESS if status_str: outbuf += '{0:65} {1:>8}'.format(ifaceline, status_str) + '\n' else: @@ -574,16 +705,18 @@ class iface(): for cname, cvaluelist in config.items(): idx = 0 for cv in cvaluelist: + status_str = None if with_status: s = self.get_config_attr_status(cname, idx) if s == -1: - status_str = unknownstr + status_str = '[%s]' %ifaceStatusUserStrs.UNKNOWN elif s == 1: - status_str = errorstr + status_str = '[%s]' %ifaceStatusUserStrs.ERROR elif s == 0: - status_str = successstr + status_str = '[%s]' %ifaceStatusUserStrs.SUCCESS + if status_str: outbuf += (indent + '{0:55} {1:>10}'.format( - '%s %s' %(cname, cv), status_str)) + '\n' + '%s %s' %(cname, cv), status_str)) + '\n' else: outbuf += indent + '%s %s\n' %(cname, cv) idx += 1 diff --git a/ifupdown/ifupdownbase.py b/ifupdown/ifupdownbase.py index 2a6b480..dbac0ad 100644 --- a/ifupdown/ifupdownbase.py +++ b/ifupdown/ifupdownbase.py @@ -8,11 +8,13 @@ # import logging -import subprocess import re import os +import traceback +from ifupdown.netlink import netlink + from iface import * -import rtnetlink_api as rtnetlink_api +import ifupdownflags as ifupdownflags class ifupdownBase(object): @@ -20,30 +22,8 @@ class ifupdownBase(object): modulename = self.__class__.__name__ self.logger = logging.getLogger('ifupdown.' + modulename) - def exec_command(self, cmd, cmdenv=None, nowait=False): - cmd_returncode = 0 - cmdout = '' - try: - self.logger.info('Executing ' + cmd) - if self.DRYRUN: - return cmdout - ch = subprocess.Popen(cmd.split(), - stdout=subprocess.PIPE, - shell=False, env=cmdenv, - stderr=subprocess.STDOUT, - close_fds=True) - cmdout = ch.communicate()[0] - cmd_returncode = ch.wait() - except OSError, e: - raise Exception('could not execute ' + cmd + - '(' + str(e) + ')') - if cmd_returncode != 0: - raise Exception('error executing cmd \'%s\'' %cmd + - '\n(' + cmdout.strip('\n ') + ')') - return cmdout - def ignore_error(self, errmsg): - if (self.FORCE == True or re.search(r'exists', errmsg, + if (ifupdownflags.flags.FORCE == True or re.search(r'exists', errmsg, re.IGNORECASE | re.MULTILINE) is not None): return True return False @@ -66,7 +46,7 @@ class ifupdownBase(object): return os.path.exists('/sys/class/net/%s' %ifacename) def link_up(self, ifacename): - rtnetlink_api.rtnl_api.link_set(ifacename, "up") + netlink.link_set_updown(ifacename, "up") def link_down(self, ifacename): - rtnetlink_api.rtnl_api.link_set(ifacename, "down") + netlink.link_set_updown(ifacename, "down") diff --git a/ifupdown/ifupdownconfig.py b/ifupdown/ifupdownconfig.py new file mode 100644 index 0000000..b156b6a --- /dev/null +++ b/ifupdown/ifupdownconfig.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python +# +# Copyright 2015 Cumulus Networks, Inc. All rights reserved. +# +# Author: Roopa Prabhu, roopa@cumulusnetworks.com +# +# + +class ifupdownConfig(): + + def __init__(self): + self.conf = {} + +config = ifupdownConfig() diff --git a/ifupdown/ifupdownflags.py b/ifupdown/ifupdownflags.py new file mode 100644 index 0000000..58ca55e --- /dev/null +++ b/ifupdown/ifupdownflags.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python +# +# Copyright 2015 Cumulus Networks, Inc. All rights reserved. +# +# Author: Roopa Prabhu, roopa@cumulusnetworks.com +# +# + +class ifupdownFlags(): + + def __init__(self): + self.ALL = False + self.FORCE = False + self.DRYRUN = False + self.NOWAIT = False + self.PERFMODE = False + self.CACHE = False + self.WITHDEFAULTS = False + + # Flags + self.CACHE_FLAGS = 0x0 + +flags = ifupdownFlags() diff --git a/ifupdown/ifupdownmain.py b/ifupdown/ifupdownmain.py index 549dd50..c9f8d33 100644 --- a/ifupdown/ifupdownmain.py +++ b/ifupdown/ifupdownmain.py @@ -15,7 +15,9 @@ import logging import sys, traceback import copy import json -from statemanager import * +import ifupdown.statemanager as statemanager +import ifupdown.ifupdownconfig as ifupdownConfig +import ifupdown.ifupdownflags as ifupdownflags from networkinterfaces import * from iface import * from scheduler import * @@ -37,35 +39,31 @@ _crossmark = u'\u2717' _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 """ - - # Flags - WITH_DEPENDS = False - ALL = False +class ifupdownMainFlags(): IFACE_CLASS = False COMPAT_EXEC_SCRIPTS = False STATEMANAGER_ENABLE = True STATEMANAGER_UPDATE = True ADDONS_ENABLE = False + DELETE_DEPENDENT_IFACES_WITH_NOCONFIG = False + SCHED_SKIP_CHECK_UPPERIFACES = False + CHECK_SHARED_DEPENDENTS = True +class ifacePrivFlags(): # priv flags to mark iface objects - BUILTIN = 0x0001 - NOCONFIG = 0x0010 + BUILTIN = False + NOCONFIG = False + + def __init__(self, builtin=False, noconfig=False): + self.BUILTIN = builtin + self.NOCONFIG = noconfig + +class ifupdownMain(ifupdownBase): + """ ifupdown2 main class """ scripts_dir='/etc/network' - addon_modules_dir='/usr/share/ifupdownaddons' - addon_modules_configfile='/var/lib/ifupdownaddons/addons.conf' + addon_modules_dir='/usr/share/ifupdown2/addons' + addon_modules_configfile='/etc/network/ifupdown2/addons.conf' # iface dictionary in the below format: # { '' : [, ..] } @@ -112,6 +110,9 @@ class ifupdownMain(ifupdownBase): # there is no real interface behind it if ifaceobj.type == ifaceType.BRIDGE_VLAN: return + if ((ifaceobj.link_kind & ifaceLinkKind.VRF) or + (ifaceobj.link_privflags & ifaceLinkPrivFlags.VRF_SLAVE)): + return if (ifaceobj.addr_method and ifaceobj.addr_method == 'manual'): return @@ -129,6 +130,9 @@ class ifupdownMain(ifupdownBase): self.link_up(ifaceobj.name) def run_down(self, ifaceobj): + if ((ifaceobj.link_kind & ifaceLinkKind.VRF) or + (ifaceobj.link_privflags & ifaceLinkPrivFlags.VRF_SLAVE)): + return # 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: @@ -154,10 +158,10 @@ class ifupdownMain(ifupdownBase): ('down', run_down)]) def run_sched_ifaceobj_posthook(self, ifaceobj, op): - if ((ifaceobj.priv_flags & self.BUILTIN) or - (ifaceobj.priv_flags & self.NOCONFIG)): + if (ifaceobj.priv_flags and (ifaceobj.priv_flags.BUILTIN or + ifaceobj.priv_flags.NOCONFIG)): return - if self.STATEMANAGER_UPDATE: + if self.flags.STATEMANAGER_UPDATE: self.statemanager.ifaceobj_sync(ifaceobj, op) # ifupdown object interface scheduler pre and posthooks @@ -169,7 +173,8 @@ class ifupdownMain(ifupdownBase): cache=False, addons_enable=True, statemanager_enable=True, interfacesfile='/etc/network/interfaces', interfacesfileiobuf=None, - interfacesfileformat='native'): + interfacesfileformat='native', + withdefaults=False): """This member function initializes the ifupdownmain object. Kwargs: @@ -184,33 +189,31 @@ class ifupdownMain(ifupdownBase): AttributeError, KeyError """ self.logger = logging.getLogger('ifupdown') - self.FORCE = force - self.DRYRUN = dryrun - self.NOWAIT = nowait - self.PERFMODE = perfmode - self.WITH_DEPENDS = withdepends - self.STATEMANAGER_ENABLE = statemanager_enable - self.CACHE = cache + ifupdownflags.flags.FORCE = force + ifupdownflags.flags.DRYRUN = dryrun + ifupdownflags.flags.WITHDEFAULTS = withdefaults + ifupdownflags.flags.NOWAIT = nowait + ifupdownflags.flags.PERFMODE = perfmode + ifupdownflags.flags.CACHE = cache + ifupdownflags.flags.WITH_DEPENDS = withdepends + + # Can be used to provide hints for caching + ifupdownflags.flags.CACHE_FLAGS = 0x0 + + self.flags = ifupdownMainFlags() + + self.flags.STATEMANAGER_ENABLE = statemanager_enable self.interfacesfile = interfacesfile self.interfacesfileiobuf = interfacesfileiobuf self.interfacesfileformat = interfacesfileformat self.config = config self.logger.debug(self.config) + self.blacklisted_ifaces_present = False 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.flags.DELETE_DEPENDENT_IFACES_WITH_NOCONFIG = False + self.flags.ADDONS_ENABLE = addons_enable self.ifaces = OrderedDict() self.njobs = njobs @@ -219,22 +222,22 @@ class ifupdownMain(ifupdownBase): self.module_attrs = {} self.load_addon_modules(self.addon_modules_dir) - if self.COMPAT_EXEC_SCRIPTS: + if self.flags.COMPAT_EXEC_SCRIPTS: self.load_scripts(self.scripts_dir) self.dependency_graph = OrderedDict({}) self._cache_no_repeats = {} - if self.STATEMANAGER_ENABLE: + if self.flags.STATEMANAGER_ENABLE: try: - self.statemanager = stateManager() + self.statemanager = statemanager.statemanager_api self.statemanager.read_saved_state() except Exception, e: # XXX Maybe we should continue by ignoring old state self.logger.warning('error reading state (%s)' %str(e)) raise else: - self.STATEMANAGER_UPDATE = False + self.flags.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 = [] @@ -249,6 +252,22 @@ class ifupdownMain(ifupdownBase): 'state changes will be delayed till the ' + 'masters admin state change.') + # squash iface objects for same interface both internal and + # external representation. It is off by default. + self._ifaceobj_squash = True if self.config.get( + 'ifaceobj_squash', '0') == '1' else False + + # squash iface objects for same interface internal + # representation only. External representation as seen by ifquery + # will continue to see multiple iface stanzas if it was specified + # that way by the user. It is on by default. + self._ifaceobj_squash_internal = True if self.config.get( + 'ifaceobj_squash_internal', '1') == '1' else False + + # initialize global config object with config passed by the user + # This makes config available to addon modules + ifupdownConfig.config = self.config + 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 @@ -269,7 +288,7 @@ class ifupdownMain(ifupdownBase): def get_ifaceobjs_saved(self, ifacename): """ Return ifaceobjects from statemanager """ - if self.STATEMANAGER_ENABLE: + if self.flags.STATEMANAGER_ENABLE: return self.statemanager.get_ifaceobjs(ifacename) else: None @@ -322,7 +341,7 @@ class ifupdownMain(ifupdownBase): ifaceobjcurr.name = ifaceobj.name ifaceobjcurr.type = ifaceobj.type ifaceobjcurr.lowerifaces = ifaceobj.lowerifaces - ifaceobjcurr.priv_flags = ifaceobj.priv_flags + ifaceobjcurr.priv_flags = copy.deepcopy(ifaceobj.priv_flags) ifaceobjcurr.auto = ifaceobj.auto self.ifaceobjcurrdict.setdefault(ifaceobj.name, []).append(ifaceobjcurr) @@ -367,7 +386,9 @@ class ifupdownMain(ifupdownBase): The following are currently considered builtin ifaces: - vlan interfaces in the format . """ - return (ifaceobj.priv_flags & self.BUILTIN) + if (ifaceobj.priv_flags and ifaceobj.priv_flags.BUILTIN): + return True + return False def is_ifaceobj_noconfig(self, ifaceobj): """ Returns true if iface object did not have a user defined config. @@ -375,7 +396,7 @@ class ifupdownMain(ifupdownBase): These interfaces appear only when they are dependents of interfaces which have user defined config """ - return (ifaceobj.priv_flags & self.NOCONFIG) + return (ifaceobj.priv_flags and ifaceobj.priv_flags.NOCONFIG) def is_iface_noconfig(self, ifacename): """ Returns true if iface has no config """ @@ -385,7 +406,7 @@ class ifupdownMain(ifupdownBase): return self.is_ifaceobj_noconfig(ifaceobj) def check_shared_dependents(self, ifaceobj, dlist): - """ Check if dlist intersects with any other + """ ABSOLETE: Check if dlist intersects with any other interface with slave dependents. example: bond and bridges. This function logs such errors """ @@ -395,6 +416,8 @@ class ifupdownMain(ifupdownBase): continue check_depends = False iobjs = self.get_ifaceobjs(ifacename) + if not iobjs: + continue for i in iobjs: if (i.dependency_type == ifaceDependencyType.MASTER_SLAVE): check_depends = True @@ -405,59 +428,110 @@ class ifupdownMain(ifupdownBase): %(ifaceobj.name, ifacename) + 'seem to share dependents/ports %s' %str(list(common))) + def _set_iface_role(self, ifaceobj, role, upperifaceobj): + if (self.flags.CHECK_SHARED_DEPENDENTS and + (ifaceobj.role & ifaceRole.SLAVE) and + (role == ifaceRole.SLAVE) and (upperifaceobj.role == ifaceRole.MASTER)): + self.logger.error("misconfig..? %s %s is enslaved to multiple interfaces %s" + %(ifaceobj.name, + ifaceLinkPrivFlags.get_all_str(ifaceobj.link_privflags), str(ifaceobj.upperifaces))) + ifaceobj.set_status(ifaceStatus.ERROR) + return + ifaceobj.role = role + + def _set_iface_role_n_kind(self, ifaceobj, upperifaceobj): + + if (upperifaceobj.link_kind & ifaceLinkKind.BOND): + self._set_iface_role(ifaceobj, ifaceRole.SLAVE, upperifaceobj) + ifaceobj.link_privflags |= ifaceLinkPrivFlags.BOND_SLAVE + + if (upperifaceobj.link_kind & ifaceLinkKind.BRIDGE): + self._set_iface_role(ifaceobj, ifaceRole.SLAVE, upperifaceobj) + ifaceobj.link_privflags |= ifaceLinkPrivFlags.BRIDGE_PORT + + if (ifaceobj.link_kind & ifaceLinkKind.VXLAN) \ + and (upperifaceobj.link_kind & ifaceLinkKind.BRIDGE): + upperifaceobj.link_privflags |= ifaceLinkPrivFlags.BRIDGE_VXLAN + + # vrf masters get processed after slaves, which means + # check both link_kind vrf and vrf slave + if ((upperifaceobj.link_kind & ifaceLinkKind.VRF) or + (ifaceobj.link_privflags & ifaceLinkPrivFlags.VRF_SLAVE)): + self._set_iface_role(ifaceobj, ifaceRole.SLAVE, upperifaceobj) + ifaceobj.link_privflags |= ifaceLinkPrivFlags.VRF_SLAVE + if self._link_master_slave: + if upperifaceobj.link_type == ifaceLinkType.LINK_MASTER: + ifaceobj.link_type = ifaceLinkType.LINK_SLAVE + else: + upperifaceobj.link_type = ifaceLinkType.LINK_NA + ifaceobj.link_type = ifaceLinkType.LINK_NA + + def dump_iface_dependency_info(self): + """ debug funtion to print raw dependency + info - lower and upper devices""" + + for ifacename, ifaceobjs in self.ifaceobjdict.iteritems(): + iobj = ifaceobjs[0] + self.logger.info("%s: refcnt: %d, lower: %s, upper: %s" %(ifacename, + self.get_iface_refcnt(ifacename), + str(iobj.lowerifaces) if iobj.lowerifaces else [], + str(iobj.upperifaces) if iobj.upperifaces else [])) + + 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: - if flag _DELETE_DEPENDENT_IFACES_WITH_NOCONFIG is True: + if flag DELETE_DEPENDENT_IFACES_WITH_NOCONFIG is True: we only consider devices whose configuration was specified in the network interfaces file. We delete any interface whose config was not specified except for vlan devices. vlan devices get special treatment. Even if they are not present they are created and added to the ifacesdict - elif flag _DELETE_DEPENDENT_IFACES_WITH_NOCONFIG is False: + elif flag DELETE_DEPENDENT_IFACES_WITH_NOCONFIG is False: we create objects for all dependent devices that are not present in the ifacesdict """ del_list = [] - if (upperifaceobj.dependency_type == - ifaceDependencyType.MASTER_SLAVE): - self.check_shared_dependents(upperifaceobj, dlist) - for d in dlist: dilist = self.get_ifaceobjs(d) if not dilist: ni = None if self.is_iface_builtin_byname(d): ni = self.create_n_save_ifaceobj(d, - self.BUILTIN | self.NOCONFIG, True) - elif not self._DELETE_DEPENDENT_IFACES_WITH_NOCONFIG: - ni = self.create_n_save_ifaceobj(d, self.NOCONFIG, - True) + ifacePrivFlags(True, True), True) + elif not self.flags.DELETE_DEPENDENT_IFACES_WITH_NOCONFIG: + ni = self.create_n_save_ifaceobj(d, + ifacePrivFlags(False, True), True) else: del_list.append(d) if ni: - if upperifaceobj.link_kind & \ - (ifaceLinkKind.BOND | ifaceLinkKind.BRIDGE): - ni.role |= ifaceRole.SLAVE ni.add_to_upperifaces(upperifaceobj.name) - if upperifaceobj.link_type == ifaceLinkType.LINK_MASTER: - ni.link_type = ifaceLinkType.LINK_SLAVE + self._set_iface_role_n_kind(ni, upperifaceobj) else: for di in dilist: di.inc_refcnt() di.add_to_upperifaces(upperifaceobj.name) - if upperifaceobj.link_kind & \ - (ifaceLinkKind.BOND | ifaceLinkKind.BRIDGE): - di.role |= ifaceRole.SLAVE - if upperifaceobj.link_type == ifaceLinkType.LINK_MASTER: - di.link_type = ifaceLinkType.LINK_SLAVE + self._set_iface_role_n_kind(di, upperifaceobj) for d in del_list: dlist.remove(d) - def query_dependents(self, ifaceobj, ops, ifacenames, type=None): + def preprocess_upperiface(self, lowerifaceobj, ulist, ops): + for u in ulist: + if (lowerifaceobj.upperifaces and + u in lowerifaceobj.upperifaces): + continue + lowerifaceobj.add_to_upperifaces(u) + uifacelist = self.get_ifaceobjs(u) + if uifacelist: + for ui in uifacelist: + lowerifaceobj.inc_refcnt() + self._set_iface_role_n_kind(lowerifaceobj, ui) + ui.add_to_lowerifaces(lowerifaceobj.name) + + def query_lowerifaces(self, ifaceobj, ops, ifacenames, type=None): """ Gets iface dependents by calling into respective modules """ ret_dlist = [] @@ -482,6 +556,29 @@ class ifupdownMain(ifupdownBase): if dlist: ret_dlist.extend(dlist) return list(set(ret_dlist)) + def query_upperifaces(self, ifaceobj, ops, ifacenames, type=None): + """ Gets iface upperifaces by calling into respective modules """ + ret_ulist = [] + + # Get upperifaces for interface by querying respective modules + for module in self.modules.values(): + try: + if ops[0] == 'query-running': + if (not hasattr(module, + 'get_upper_ifacenames_running')): + continue + ulist = module.get_upper_ifacenames_running(ifaceobj) + else: + if (not hasattr(module, 'get_upper_ifacenames')): + continue + ulist = module.get_upper_ifacenames(ifaceobj, ifacenames) + except Exception, e: + self.logger.warn('%s: error getting upper interfaces (%s)' + %(ifaceobj.name, str(e))) + ulist = None + pass + if ulist: ret_ulist.extend(ulist) + return list(set(ret_ulist)) def populate_dependency_info(self, ops, ifacenames=None): """ recursive function to generate iface dependency info """ @@ -494,21 +591,72 @@ class ifupdownMain(ifupdownBase): i = iqueue.popleft() # Go through all modules and find dependent ifaces dlist = None - ifaceobj = self.get_ifaceobj_first(i) - if not ifaceobj: + ulist = None + ifaceobjs = self.get_ifaceobjs(i) + if not ifaceobjs: continue - dlist = ifaceobj.lowerifaces - if not dlist: - dlist = self.query_dependents(ifaceobj, ops, ifacenames) - else: + dependents_processed = False + + # Store all dependency info in the first ifaceobj + # but get dependency info from all ifaceobjs + ifaceobj = ifaceobjs[0] + for iobj in ifaceobjs: + ulist = self.query_upperifaces(iobj, ops, ifacenames) + if iobj.lowerifaces: + dependents_processed = True + break + dlist = self.query_lowerifaces(iobj, ops, ifacenames) + if dlist: + break + if ulist: + self.preprocess_upperiface(ifaceobj, ulist, ops) + if dependents_processed: continue if dlist: 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 + #if not self.dependency_graph.get(i): + # self.dependency_graph[i] = dlist + + for i in self.ifaceobjdict.keys(): + iobj = self.get_ifaceobj_first(i) + if iobj.lowerifaces: + self.dependency_graph[i] = iobj.lowerifaces + else: + self.dependency_graph[i] = [] + + if not self.blacklisted_ifaces_present: + return + + # Walk through the dependency graph and remove blacklisted + # interfaces that were picked up as dependents + for i in self.dependency_graph.keys(): + ifaceobj = self.get_ifaceobj_first(i) + if not ifaceobj: + continue + + if ifaceobj.blacklisted and not ifaceobj.upperifaces: + # if blacklisted and was not picked up as a + # dependent of a upper interface, delete the + # interface from the dependency graph + dlist = ifaceobj.lowerifaces + if dlist: + for d in dlist: + difaceobjs = self.get_ifaceobjs(d) + if not difaceobjs: + continue + try: + for d in difaceobjs: + d.dec_refcnt() + d.upperifaces.remove(i) + except: + self.logger.debug('error removing %s from %s upperifaces' %(i, d)) + pass + self.logger.debug("populate_dependency_info: deleting blacklisted interface %s" %i) + del self.dependency_graph[i] + continue def _check_config_no_repeats(self, ifaceobj): """ check if object has an attribute that is @@ -527,21 +675,49 @@ class ifupdownMain(ifupdownBase): self._cache_no_repeats[k] = v return False - def _save_iface(self, ifaceobj): + def _save_iface_squash(self, ifaceobj): + """ squash ifaceobjects belonging to same iface + into a single object """ if self._check_config_no_repeats(ifaceobj): return + ifaceobj.priv_flags = ifacePrivFlags() 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] + self.ifaceobjdict[ifaceobj.name] = [ifaceobj] + return + if ifaceobj.compare(currentifaceobjlist[0]): + self.logger.warn('duplicate interface %s found' %ifaceobj.name) + return + if ifaceobj.type == ifaceType.BRIDGE_VLAN: + self.ifaceobjdict[ifaceobj.name].append(ifaceobj) + else: + currentifaceobjlist[0].squash(ifaceobj) + + def _save_iface(self, ifaceobj): + if self._check_config_no_repeats(ifaceobj): return + ifaceobj.priv_flags = ifacePrivFlags() + 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] + if not self._ifaceobj_squash: + ifaceobj.flags |= ifaceobj.YOUNGEST_SIBLING + return if ifaceobj.compare(currentifaceobjlist[0]): self.logger.warn('duplicate interface %s found' %ifaceobj.name) return if currentifaceobjlist[0].type == ifaceobj.type: - currentifaceobjlist[0].flags |= iface.HAS_SIBLINGS - ifaceobj.flags |= iface.HAS_SIBLINGS + currentifaceobjlist[0].flags |= ifaceobj.HAS_SIBLINGS + ifaceobj.flags |= ifaceobj.HAS_SIBLINGS + # clear the OLDEST_SIBLING from all the siblings + for iface in self.ifaceobjdict[ifaceobj.name]: + iface.flags &= ~ifaceobj.OLDEST_SIBLING + # current sibling is the oldest + ifaceobj.flags |= ifaceobj.OLDEST_SIBLING self.ifaceobjdict[ifaceobj.name].append(ifaceobj) def _iface_configattr_syntax_checker(self, attrname, attrval): @@ -550,14 +726,22 @@ class ifupdownMain(ifupdownBase): continue attrsdict = mdict.get('attrs') try: - if attrsdict.get(attrname): + a = attrsdict.get(attrname) + if a: + if a.get('deprecated'): + newa = a.get('new-attribute') + if newa: + self.logger.warn('attribute %s is deprecated. use %s instead.' %(attrname, newa)) + else: + self.logger.warn('attribute %s is deprecated.' + %attrname) return True except AttributeError: pass return False def _ifaceobj_syntax_checker(self, ifaceobj): - err = False + ret = True for attrname, attrvalue in ifaceobj.config.items(): found = False for k, v in self.module_attrs.items(): @@ -565,24 +749,31 @@ class ifupdownMain(ifupdownBase): found = True break if not found: - err = True + ret = False self.logger.warn('%s: unsupported attribute \'%s\'' \ % (ifaceobj.name, attrname)) continue - return err + return ret def read_iface_config(self): """ Reads default network interface config /etc/network/interfaces. """ + ret = True nifaces = networkInterfaces(self.interfacesfile, self.interfacesfileiobuf, self.interfacesfileformat, template_engine=self.config.get('template_engine'), template_lookuppath=self.config.get('template_lookuppath')) - nifaces.subscribe('iface_found', self._save_iface) + if self._ifaceobj_squash or self._ifaceobj_squash_internal: + nifaces.subscribe('iface_found', self._save_iface_squash) + else: + nifaces.subscribe('iface_found', self._save_iface) nifaces.subscribe('validateifaceattr', self._iface_configattr_syntax_checker) nifaces.subscribe('validateifaceobj', self._ifaceobj_syntax_checker) nifaces.load() + if nifaces.errors or nifaces.warns: + ret = False + return ret def read_old_iface_config(self): """ Reads the saved iface config instead of default iface config. @@ -603,7 +794,7 @@ class ifupdownMain(ifupdownBase): mname = litems[1] self.module_ops[operation].append(mname) except Exception, e: - self.logger.warn('error reading line \'%s\'' %(l, str(e))) + self.logger.warn('error reading line \'%s\' %s:' %(l, str(e))) continue def load_addon_modules(self, modules_dir): @@ -628,12 +819,7 @@ class ifupdownMain(ifupdownBase): mclass = getattr(m, mname) except: raise - minstance = mclass(force=self.FORCE, - dryrun=self.DRYRUN, - nowait=self.NOWAIT, - perfmode=self.PERFMODE, - cache=self.CACHE, - cacheflags=self.CACHE_FLAGS) + minstance = mclass() self.modules[mname] = minstance try: self.module_attrs[mname] = minstance.get_modinfo() @@ -724,14 +910,15 @@ class ifupdownMain(ifupdownBase): %(str(ops), str(ifacenames))) self._pretty_print_ordered_dict('dependency graph', self.dependency_graph) - return ifaceScheduler.sched_ifaces(self, ifacenames, ops, + ifaceScheduler.sched_ifaces(self, ifacenames, ops, dependency_graph=self.dependency_graph, order=ifaceSchedulerFlags.INORDER if 'down' in ops[0] else ifaceSchedulerFlags.POSTORDER, followdependents=followdependents, skipupperifaces=skipupperifaces, - sort=True if (sort or self.IFACE_CLASS) else False) + sort=True if (sort or self.flags.IFACE_CLASS) else False) + return ifaceScheduler.get_sched_status() def _render_ifacename(self, ifacename): new_ifacenames = [] @@ -778,29 +965,56 @@ class ifupdownMain(ifupdownBase): interfaces are checked against the allow_classes and auto lists. """ + + ret = True + + # Check if interface matches the exclude patter if excludepats: for e in excludepats: if re.search(e, ifacename): - return False + ret = False ifaceobjs = self.get_ifaceobjs(ifacename) if not ifaceobjs: - self.logger.debug('iface %s' %ifacename + ' not found') - return False - # We check classes first + if ret: + self.logger.debug('iface %s' %ifacename + ' not found') + return ret + # If matched exclude pattern, return false + if not ret: + for i in ifaceobjs: + i.blacklisted = True + self.blacklisted_ifaces_present = True + return ret + # Check if interface belongs to the class + # the user is interested in, if not return false if allow_classes: + ret = False for i in ifaceobjs: if i.classes: common = Set([allow_classes]).intersection( Set(i.classes)) if common: - return True - return False + ret = True + if not ret: + # If a class was requested and interface does not belong + # to the class, only then mark the ifaceobjs as blacklisted + self.blacklisted_ifaces_present = True + for i in ifaceobjs: + i.blacklisted = True + return ret + # If the user has requested auto class, check if the interface + # is marked auto if auto: + ret = False for i in ifaceobjs: if i.auto: - return True - return False - return True + ret = True + if not ret: + # If auto was requested and interface was not marked auto, + # only then mark all of them as blacklisted + self.blacklisted_ifaces_present = True + for i in ifaceobjs: + i.blacklisted = True + return ret def _compat_conv_op_to_mode(self, op): """ Returns old op name to work with existing scripts """ @@ -828,7 +1042,8 @@ class ifupdownMain(ifupdownBase): return cenv def _save_state(self): - if not self.STATEMANAGER_ENABLE or not self.STATEMANAGER_UPDATE: + if (not self.flags.STATEMANAGER_ENABLE or + not self.flags.STATEMANAGER_UPDATE): return try: # Update persistant iface states @@ -884,20 +1099,17 @@ class ifupdownMain(ifupdownBase): self.set_type(type) if allow_classes: - self.IFACE_CLASS = True - if not self.ADDONS_ENABLE: self.STATEMANAGER_UPDATE = False + self.flags.IFACE_CLASS = True + if not self.flags.ADDONS_ENABLE: + self.flags.STATEMANAGER_UPDATE = False if auto: - self.ALL = True - self.WITH_DEPENDS = True + ifupdownflags.flags.ALL = True + ifupdownflags.flags.WITH_DEPENDS = True try: - self.read_iface_config() + iface_read_ret = self.read_iface_config() except Exception: raise - # If only syntax check was requested, return here - if syntaxcheck: - return - if ifacenames: ifacenames = self._preprocess_ifacenames(ifacenames) @@ -918,15 +1130,31 @@ class ifupdownMain(ifupdownBase): else: self.populate_dependency_info(ops) + # If only syntax check was requested, return here. + # return here because we want to make sure most + # errors above are caught and reported. + if syntaxcheck: + if not iface_read_ret: + raise Exception() + elif self._any_iface_errors(filtered_ifacenames): + raise Exception() + return + + ret = None try: - self._sched_ifaces(filtered_ifacenames, ops, - skipupperifaces=skipupperifaces, - followdependents=True if self.WITH_DEPENDS else False) + ret = self._sched_ifaces(filtered_ifacenames, ops, + skipupperifaces=skipupperifaces, + followdependents=True + if ifupdownflags.flags.WITH_DEPENDS + else False) finally: self._process_delay_admin_state_queue('up') - if not self.DRYRUN and self.ADDONS_ENABLE: + if not ifupdownflags.flags.DRYRUN and self.flags.ADDONS_ENABLE: self._save_state() + if not iface_read_ret or not ret: + raise Exception() + def down(self, ops, auto=False, allow_classes=None, ifacenames=None, excludepats=None, printdependency=None, usecurrentconfig=False, type=None): @@ -935,14 +1163,15 @@ class ifupdownMain(ifupdownBase): self.set_type(type) if allow_classes: - self.IFACE_CLASS = True - if not self.ADDONS_ENABLE: self.STATEMANAGER_UPDATE = False + self.flags.IFACE_CLASS = True + if not self.flags.ADDONS_ENABLE: + self.flags.STATEMANAGER_UPDATE = False if auto: - self.ALL = True - self.WITH_DEPENDS = True + ifupdownflags.flags.ALL = True + ifupdownflags.flags.WITH_DEPENDS = True # For down we need to look at old state, unless usecurrentconfig # is set - if (not usecurrentconfig and self.STATEMANAGER_ENABLE and + if (not usecurrentconfig and self.flags.STATEMANAGER_ENABLE and self.statemanager.ifaceobjdict): # Since we are using state manager objects, # skip the updating of state manager objects @@ -983,36 +1212,43 @@ class ifupdownMain(ifupdownBase): try: self._sched_ifaces(filtered_ifacenames, ops, - followdependents=True if self.WITH_DEPENDS else False) + followdependents=True + if ifupdownflags.flags.WITH_DEPENDS else False) finally: self._process_delay_admin_state_queue('down') - if not self.DRYRUN and self.ADDONS_ENABLE: + if not ifupdownflags.flags.DRYRUN and self.flags.ADDONS_ENABLE: self._save_state() - def query(self, ops, auto=False, allow_classes=None, ifacenames=None, + def query(self, ops, auto=False, format_list=False, allow_classes=None, + ifacenames=None, excludepats=None, printdependency=None, format='native', type=None): """ query an interface """ self.set_type(type) + # Let us forget internal squashing when it comes to + # ifquery. It can surprise people relying of ifquery + # output + self._ifaceobj_squash_internal = False + if allow_classes: - self.IFACE_CLASS = True - if self.STATEMANAGER_ENABLE and ops[0] == 'query-savedstate': + self.flags.IFACE_CLASS = True + if self.flags.STATEMANAGER_ENABLE and ops[0] == 'query-savedstate': return self.statemanager.dump_pretty(ifacenames) - self.STATEMANAGER_UPDATE = False + self.flags.STATEMANAGER_UPDATE = False if auto: self.logger.debug('setting flag ALL') - self.ALL = True - self.WITH_DEPENDS = True + ifupdownflags.flags.ALL = True + ifupdownflags.flags.WITH_DEPENDS = True if ops[0] == 'query-syntax': self._modules_help() return elif ops[0] == 'query-running': # create fake devices to all dependents that dont have config - map(lambda i: self.create_n_save_ifaceobj(i, self.NOCONFIG), - ifacenames) + map(lambda i: self.create_n_save_ifaceobj(i, + ifacePrivFlags(False, True)), ifacenames) else: try: self.read_iface_config() @@ -1042,15 +1278,21 @@ class ifupdownMain(ifupdownBase): self.print_dependency(filtered_ifacenames, printdependency) return - if ops[0] == 'query': + if format_list and (ops[0] == 'query' or ops[0] == 'query-raw'): + return self.print_ifaceobjs_list(filtered_ifacenames) + + if ops[0] == 'query' and not ifupdownflags.flags.WITHDEFAULTS: return self.print_ifaceobjs_pretty(filtered_ifacenames, format) elif ops[0] == 'query-raw': return self.print_ifaceobjs_raw(filtered_ifacenames) - self._sched_ifaces(filtered_ifacenames, ops, - followdependents=True if self.WITH_DEPENDS else False) + ret = self._sched_ifaces(filtered_ifacenames, ops, + followdependents=True + if ifupdownflags.flags.WITH_DEPENDS else False) - if ops[0] == 'query-checkcurr': + if ops[0] == 'query' and ifupdownflags.flags.WITHDEFAULTS: + return self.print_ifaceobjs_pretty(filtered_ifacenames, format) + elif ops[0] == 'query-checkcurr': ret = self.print_ifaceobjscurr_pretty(filtered_ifacenames, format) if ret != 0: # if any of the object has an error, signal that silently @@ -1059,42 +1301,49 @@ class ifupdownMain(ifupdownBase): self.print_ifaceobjsrunning_pretty(filtered_ifacenames, format) return - def _reload_currentlyup(self, upops, downops, auto=True, allow=None, + def _reload_currentlyup(self, upops, downops, auto=False, allow=None, ifacenames=None, excludepats=None, usecurrentconfig=False, - **extra_args): + syntaxcheck=False, **extra_args): """ reload currently up interfaces """ - allow_classes = [] new_ifaceobjdict = {} - # Override auto to true - auto = True + self.logger.info('reloading interfaces that are currently up ..') + try: - self.read_iface_config() + iface_read_ret = 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 + if not ifacenames: ifacenames = self.ifaceobjdict.keys() + + if (not usecurrentconfig and self.flags.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) + + interfaces_to_up = already_up_ifacenames_still_present + + # generate dependency graph of interfaces + self.populate_dependency_info(upops, interfaces_to_up) + + # If only syntax check was requested, return here. + # return here because we want to make sure most + # errors above are caught and reported. + if syntaxcheck: + if not iface_read_ret: + raise Exception() + elif self._any_iface_errors(interfaces_to_up): + raise Exception() + return if (already_up_ifacenames_not_present and self.config.get('ifreload_currentlyup_down_notpresent') == '1'): @@ -1110,56 +1359,83 @@ class ifupdownMain(ifupdownBase): # reinitialize dependency graph self.dependency_graph = OrderedDict({}) + falready_up_ifacenames_not_present = [i for i in + already_up_ifacenames_not_present + if self._iface_whitelisted(auto, allow, + excludepats, i)] self.populate_dependency_info(downops, - already_up_ifacenames_not_present) - self._sched_ifaces(already_up_ifacenames_not_present, downops, + falready_up_ifacenames_not_present) + self._sched_ifaces(falready_up_ifacenames_not_present, downops, followdependents=False, sort=True) else: - self.logger.debug('no interfaces to down ..') + self.logger.info('no interfaces to down ..') # Now, run 'up' with new config dict # reset statemanager update flag to default if auto: - self.ALL = True - self.WITH_DEPENDS = True + ifupdownflags.flags.ALL = True + ifupdownflags.flags.WITH_DEPENDS = True if new_ifaceobjdict: # and now, ifaceobjdict is back to current config self.ifaceobjdict = new_ifaceobjdict self.dependency_graph = new_dependency_graph if not self.ifaceobjdict: - return + self.logger.info('no interfaces to up') + return self.logger.info('reload: scheduling up on interfaces: %s' %str(interfaces_to_up)) - self._sched_ifaces(interfaces_to_up, upops, - followdependents=True if self.WITH_DEPENDS else False) - if self.DRYRUN: + ret = self._sched_ifaces(interfaces_to_up, upops, + followdependents=True + if ifupdownflags.flags.WITH_DEPENDS else False) + if ifupdownflags.flags.DRYRUN: return self._save_state() + if not iface_read_ret or not ret: + raise Exception() + def _reload_default(self, upops, downops, auto=False, allow=None, ifacenames=None, excludepats=None, usecurrentconfig=False, - **extra_args): + syntaxcheck=False, **extra_args): """ reload interface config """ - allow_classes = [] new_ifaceobjdict = {} try: - self.read_iface_config() + iface_read_ret = self.read_iface_config() except: raise if not self.ifaceobjdict: self.logger.warn("nothing to reload ..exiting.") return + + if not ifacenames: ifacenames = self.ifaceobjdict.keys() + new_filtered_ifacenames = [i for i in ifacenames + if self._iface_whitelisted(auto, allow, + excludepats, i)] # generate dependency graph of interfaces self.populate_dependency_info(upops) - if (not usecurrentconfig and self.STATEMANAGER_ENABLE + + # If only syntax check was requested, return here. + # return here because we want to make sure most + # errors above are caught and reported. + if syntaxcheck: + if not iface_read_ret: + raise Exception() + elif self._any_iface_errors(new_filtered_ifacenames): + raise Exception() + return + + if (not usecurrentconfig and self.flags.STATEMANAGER_ENABLE and self.statemanager.ifaceobjdict): # Save a copy of new iface objects and dependency_graph new_ifaceobjdict = dict(self.ifaceobjdict) new_dependency_graph = dict(self.dependency_graph) + self.ifaceobjdict = OrderedDict({}) + self.dependency_graph = OrderedDict({}) + # if old state is present, read old state and mark op for 'down' # followed by 'up' aka: reload # old interface config is read into self.ifaceobjdict @@ -1168,13 +1444,30 @@ class ifupdownMain(ifupdownBase): else: # oldconfig not available, continue with 'up' with new config op = 'up' + new_ifaceobjdict = self.ifaceobjdict + new_dependency_graph = self.dependency_graph - if not ifacenames: ifacenames = self.ifaceobjdict.keys() if op == 'reload' and ifacenames: - filtered_ifacenames = [i for i in ifacenames - if self._iface_whitelisted(auto, allow_classes, + ifacenames = self.ifaceobjdict.keys() + old_filtered_ifacenames = [i for i in ifacenames + if self._iface_whitelisted(auto, allow, excludepats, i)] + # generate dependency graph of old interfaces, + # This should make sure built in interfaces are + # populated. disable check shared dependents as an optimization. + # these are saved interfaces and dependency for these + # have been checked before they became part of saved state. + try: + self.flags.CHECK_SHARED_DEPENDENTS = False + self.populate_dependency_info(upops) + self.flags.CHECK_SHARED_DEPENDENTS = True + except Exception, e: + self.logger.info("error generating dependency graph for " + "saved interfaces (%s)" %str(e)) + pass + + # make sure we pick up built-in interfaces # if config file had 'ifreload_down_changed' variable # set, also look for interfaces that changed to down them down_changed = int(self.config.get('ifreload_down_changed', '1')) @@ -1186,8 +1479,13 @@ class ifupdownMain(ifupdownBase): # - interfaces that were changed between the last and current # config ifacedownlist = [] - for ifname in filtered_ifacenames: + for ifname in self.ifaceobjdict.keys(): lastifaceobjlist = self.ifaceobjdict.get(ifname) + if not self.is_ifaceobj_builtin(lastifaceobjlist[0]): + # if interface is not built-in and is not in + # old filtered ifacenames + if ifname not in old_filtered_ifacenames: + continue objidx = 0 # If interface is not present in the new file # append it to the down list @@ -1195,6 +1493,20 @@ class ifupdownMain(ifupdownBase): if not newifaceobjlist: ifacedownlist.append(ifname) continue + # If ifaceobj was present in the old interfaces file, + # and does not have a config in the new interfaces file + # but has been picked up as a dependent of another + # interface, catch it here. This catches a common error + # for example: remove a bond section from the interfaces + # file, but leave it around as a bridge port + # XXX: Ideally its better to just add it to the + # ifacedownlist. But we will be cautious here + # and just print a warning + if (self.is_ifaceobj_noconfig(newifaceobjlist[0]) and + not self.is_ifaceobj_builtin(newifaceobjlist[0]) and + lastifaceobjlist[0].is_config_present() and + lastifaceobjlist[0].link_kind): + self.logger.warn('%s: misconfig ? removed but still exists as a dependency of %s' %(newifaceobjlist[objidx].name, str(newifaceobjlist[objidx].upperifaces))) if not down_changed: continue if len(newifaceobjlist) != len(lastifaceobjlist): @@ -1216,9 +1528,21 @@ class ifupdownMain(ifupdownBase): %str(ifacedownlist)) # reinitialize dependency graph self.dependency_graph = OrderedDict({}) + # Generate dependency info for old config + self.flags.CHECK_SHARED_DEPENDENTS = False self.populate_dependency_info(downops, ifacedownlist) + self.flags.CHECK_SHARED_DEPENDENTS = True + try: + # XXX: Hack to skip checking upperifaces during down. + # the dependency list is not complete here + # and we dont want to down the upperiface. + # Hence during reload, set this to true. + # This is being added to avoid a failure in + # scheduler._check_upperifaces when we are dowing + # a builtin bridge port + self.flags.SCHED_SKIP_CHECK_UPPERIFACES = True self._sched_ifaces(ifacedownlist, downops, followdependents=False, sort=True) @@ -1226,40 +1550,44 @@ class ifupdownMain(ifupdownBase): self.logger.error(str(e)) pass finally: + self.flags.SCHED_SKIP_CHECK_UPPERIFACES = False self._process_delay_admin_state_queue('down') else: - self.logger.debug('no interfaces to down ..') + self.logger.info('no interfaces to down ..') # Now, run 'up' with new config dict # reset statemanager update flag to default if not new_ifaceobjdict: + self.logger.debug('no interfaces to up') return if auto: - self.ALL = True - self.WITH_DEPENDS = True + ifupdownflags.flags.ALL = True + ifupdownflags.flags.WITH_DEPENDS = True # and now, we are back to the current config in ifaceobjdict self.ifaceobjdict = new_ifaceobjdict self.dependency_graph = new_dependency_graph - ifacenames = self.ifaceobjdict.keys() - filtered_ifacenames = [i for i in ifacenames - if self._iface_whitelisted(auto, allow_classes, - excludepats, i)] self.logger.info('reload: scheduling up on interfaces: %s' - %str(filtered_ifacenames)) + %str(new_filtered_ifacenames)) + ifupdownflags.flags.CACHE = True try: - self._sched_ifaces(filtered_ifacenames, upops, - followdependents=True if self.WITH_DEPENDS else False) + ret = self._sched_ifaces(new_filtered_ifacenames, upops, + followdependents=True + if ifupdownflags.flags.WITH_DEPENDS + else False) except Exception, e: + ret = None self.logger.error(str(e)) - pass finally: self._process_delay_admin_state_queue('up') - if self.DRYRUN: + if ifupdownflags.flags.DRYRUN: return self._save_state() + if not iface_read_ret or not ret: + raise Exception() + def reload(self, *args, **kargs): """ reload interface config """ self.logger.debug('reloading interface config ..') @@ -1268,6 +1596,16 @@ class ifupdownMain(ifupdownBase): else: self._reload_default(*args, **kargs) + def _any_iface_errors(self, ifacenames): + for i in ifacenames: + ifaceobjs = self.get_ifaceobjs(i) + if not ifaceobjs: continue + for ifaceobj in ifaceobjs: + if (ifaceobj.status == ifaceStatus.NOTFOUND or + ifaceobj.status == ifaceStatus.ERROR): + return True + return False + def _pretty_print_ordered_dict(self, prefix, argdict): outbuf = prefix + ' {\n' for k, vlist in argdict.items(): @@ -1289,6 +1627,10 @@ class ifupdownMain(ifupdownBase): self.dependency_graph.keys()) graph.generate_dots(self.dependency_graph, indegrees) + def print_ifaceobjs_list(self, ifacenames): + for i in ifacenames: + print i + def print_ifaceobjs_raw(self, ifacenames): """ prints raw lines for ifaces from config file """ @@ -1299,7 +1641,8 @@ class ifupdownMain(ifupdownBase): continue ifaceobj.dump_raw(self.logger) print '\n' - if self.WITH_DEPENDS and not self.ALL: + if (ifupdownflags.flags.WITH_DEPENDS and + not ifupdownflags.flags.ALL): dlist = ifaceobj.lowerifaces if not dlist: continue self.print_ifaceobjs_raw(dlist) @@ -1313,7 +1656,8 @@ class ifupdownMain(ifupdownBase): (running and not ifaceobj.is_config_present())): continue ifaceobjs.append(ifaceobj) - if self.WITH_DEPENDS and not self.ALL: + if (ifupdownflags.flags.WITH_DEPENDS and + not ifupdownflags.flags.ALL): dlist = ifaceobj.lowerifaces if not dlist: continue self._get_ifaceobjs_pretty(dlist, ifaceobjs, running) @@ -1349,7 +1693,8 @@ class ifupdownMain(ifupdownBase): if self.is_ifaceobj_noconfig(ifaceobj): continue ifaceobjs.append(ifaceobj) - if self.WITH_DEPENDS and not self.ALL: + if (ifupdownflags.flags.WITH_DEPENDS and + not ifupdownflags.flags.ALL): dlist = ifaceobj.lowerifaces if not dlist: continue dret = self._get_ifaceobjscurr_pretty(dlist, ifaceobjs) @@ -1366,16 +1711,16 @@ class ifupdownMain(ifupdownBase): ifaceobjs = [] ret = self._get_ifaceobjscurr_pretty(ifacenames, ifaceobjs) if not ifaceobjs: return + + # override ifaceStatusUserStrs + ifaceStatusUserStrs.SUCCESS = self.config.get('ifquery_check_success_str', _success_sym) + ifaceStatusUserStrs.ERROR = self.config.get('ifquery_check_error_str', _error_sym) + ifaceStatusUserStrs.UNKNOWN = self.config.get('ifquery_check_unknown_str', '') if format == 'json': - print json.dumps(ifaceobjs, cls=ifaceJsonEncoder, indent=2, - separators=(',', ': ')) + print json.dumps(ifaceobjs, cls=ifaceJsonEncoderWithStatus, + indent=2, separators=(',', ': ')) else: - map(lambda i: i.dump_pretty(with_status=True, - 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) + map(lambda i: i.dump_pretty(with_status=True), ifaceobjs) return ret def print_ifaceobjsrunning_pretty(self, ifacenames, format='native'): diff --git a/ifupdown/netlink.py b/ifupdown/netlink.py index 5a01043..fda8039 100644 --- a/ifupdown/netlink.py +++ b/ifupdown/netlink.py @@ -1,241 +1,91 @@ -#!/usr/bin/env python +#!/usr/bin/python # -# Copyright 2014 Cumulus Networks, Inc. All rights reserved. -# Author: Scott Feldman, sfeldma@cumulusnetworks.com +# Copyright 2016 Cumulus Networks, Inc. All rights reserved. +# Author: Julien Fortin, julien@cumulusnetworks.com # -from os import strerror -import select -from time import time -import socket -from ctypes import * -from errno import * -import logging +try: -logger = logging.getLogger(__name__) + from ifupdownaddons.utilsbase import utilsBase + from nlmanager.nlmanager import NetlinkManager + import ifupdown.ifupdownflags as ifupdownflags +except ImportError, e: + raise ImportError(str(e) + "- required module not found") -# -# 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()) +class Netlink(utilsBase): + def __init__(self, *args, **kargs): + utilsBase.__init__(self, *args, **kargs) + self._nlmanager_api = NetlinkManager() + def get_iface_index(self, ifacename): + self.logger.info('netlink: %s: get iface index' % ifacename) + if ifupdownflags.flags.DRYRUN: return try: + return self._nlmanager_api.get_iface_index(ifacename) + except Exception as e: + raise Exception('netlink: %s: cannot get ifindex: %s' + % (ifacename, str(e))) - 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 - + def link_add_vlan(self, vlanrawdevice, ifacename, vlanid): + self.logger.info('netlink: %s: creating vlan %s' % (vlanrawdevice, vlanid)) + if ifupdownflags.flags.DRYRUN: return + ifindex = self.get_iface_index(vlanrawdevice) try: - socket.socket.bind(self, (self.pid, groups)) + return self._nlmanager_api.link_add_vlan(ifindex, ifacename, vlanid) + except Exception as e: + raise Exception('netlink: %s: cannot create vlan %s: %s' + % (vlanrawdevice, vlanid, str(e))) - except socket.error as (errno, string): - raise NetlinkError("bind: socket err[%d]: %s" % \ - (errno, string)) - - def sendall(self, string): + def link_add_macvlan(self, ifacename, macvlan_ifacename): + self.logger.info('netlink: %s: creating macvlan %s' + % (ifacename, macvlan_ifacename)) + if ifupdownflags.flags.DRYRUN: return + ifindex = self.get_iface_index(ifacename) 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 + return self._nlmanager_api.link_add_macvlan(ifindex, macvlan_ifacename) + except Exception as e: + raise Exception('netlink: %s: cannot create macvlan %s: %s' + % (ifacename, macvlan_ifacename, str(e))) + def link_set_updown(self, ifacename, state): + self.logger.info('netlink: set link %s %s' % (ifacename, state)) + if ifupdownflags.flags.DRYRUN: return try: - recv, src_addr = self.recvfrom_into(self.recvbuf) - if not recv: - # EOF - print "EOF" - return False + return self._nlmanager_api.link_set_updown(ifacename, state) + except Exception as e: + raise Exception('netlink: cannot set link %s %s: %s' + % (ifacename, state, str(e))) - except socket.error as (errno, string): - if errno in [EINTR, EAGAIN]: - return False - raise NetlinkError("netlink: socket err[%d]: %s" % \ - (errno, string)) + def link_set_protodown(self, ifacename, state): + self.logger.info('netlink: set link %s protodown %s' % (ifacename, state)) + if ifupdownflags.flags.DRYRUN: return + try: + return self._nlmanager_api.link_set_protodown(ifacename, state) + except Exception as e: + raise Exception('netlink: cannot set link %s protodown %s: %s' + % (ifacename, state, str(e))) - nlh = Nlmsghdr.from_buffer(self.recvbuf) - for recv, nlh in self._process_nlh(recv, nlh): + def link_add_bridge_vlan(self, ifacename, vlanid): + self.logger.info('netlink: %s: creating bridge vlan %s' + % (ifacename, vlanid)) + if ifupdownflags.flags.DRYRUN: return + ifindex = self.get_iface_index(ifacename) + try: + return self._nlmanager_api.link_add_bridge_vlan(ifindex, vlanid) + except Exception as e: + raise Exception('netlink: %s: cannot create bridge vlan %s: %s' + % (ifacename, vlanid, str(e))) -# print "type %u, seq %u, pid %u" % \ -# (nlh.nlmsg_type, nlh.nlmsg_seq, nlh.nlmsg_pid) + def link_del_bridge_vlan(self, ifacename, vlanid): + self.logger.info('netlink: %s: removing bridge vlan %s' + % (ifacename, vlanid)) + if ifupdownflags.flags.DRYRUN: return + ifindex = self.get_iface_index(ifacename) + try: + return self._nlmanager_api.link_del_bridge_vlan(ifindex, vlanid) + except Exception as e: + raise Exception('netlink: %s: cannot remove bridge vlan %s: %s' + % (ifacename, vlanid, str(e))) - 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() +netlink = Netlink() diff --git a/ifupdown/networkinterfaces.py b/ifupdown/networkinterfaces.py index f241e45..eacf386 100644 --- a/ifupdown/networkinterfaces.py +++ b/ifupdown/networkinterfaces.py @@ -64,6 +64,9 @@ class networkInterfaces(): self._currentfile_has_template = False self._ws_split_regex = re.compile(r'[\s\t]\s*') + self.errors = 0 + self.warns = 0 + @property def _currentfile(self): try: @@ -76,12 +79,14 @@ class networkInterfaces(): self.logger.error('%s: %s' %(filename, msg)) else: self.logger.error('%s: line%d: %s' %(filename, lineno, msg)) + self.errors += 1 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)) + self.warns += 1 def _validate_addr_family(self, ifaceobj, lineno=-1): if ifaceobj.addr_family: @@ -149,7 +154,8 @@ class networkInterfaces(): if sourced_file: filenames = glob.glob(sourced_file) if not filenames: - self._parse_warn(self._currentfile, lineno, + if '*' not in sourced_file: + self._parse_warn(self._currentfile, lineno, 'cannot find source file %s' %sourced_file) return 0 for f in filenames: @@ -219,6 +225,10 @@ class networkInterfaces(): iface_attrs = re.split(self._ws_split_regex, iface_line) ifacename = iface_attrs[1] + if utils.check_ifname_size_invalid(ifacename): + self._parse_warn(self._currentfile, lineno, + '%s: interface name too long' %ifacename) + # 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 @@ -327,8 +337,8 @@ class networkInterfaces(): def _is_keyword(self, str): # The additional split here is for allow- keyword - tmp_str = str.split('-')[0] - if tmp_str in self.network_elems.keys(): + if (str in self.network_elems.keys() or + str.split('-')[0] == 'allow'): return 1 return 0 @@ -397,9 +407,13 @@ class networkInterfaces(): return self._filestack.append(filename) self.logger.info('processing interfaces file %s' %filename) - f = open(filename) - filedata = f.read() - f.close() + try: + with open(filename) as f: + filedata = f.read() + except Exception, e: + self.logger.warn('error processing file %s (%s)', + filename, str(e)) + return self.read_filedata(filedata) self._filestack.pop() @@ -409,20 +423,23 @@ class networkInterfaces(): #object_hook=ifaceJsonDecoder.json_object_hook) elif filename: self.logger.info('processing interfaces file %s' %filename) - fp = open(filename) - ifacedicts = json.load(fp) + with open(filename) as fp: + 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] + errors = 0 for ifacedict in ifacedicts: ifaceobj = ifaceJsonDecoder.json_to_ifaceobj(ifacedict) if ifaceobj: self._validate_addr_family(ifaceobj) - self.callbacks.get('validateifaceobj')(ifaceobj) + if not self.callbacks.get('validateifaceobj')(ifaceobj): + errors += 1 self.callbacks.get('iface_found')(ifaceobj) + self.errors += errors def load(self): """ This member function loads the networkinterfaces file. @@ -430,6 +447,12 @@ class networkInterfaces(): Assumes networkinterfaces parser object is initialized with the parser arguments """ + if not self.interfacesfile and not self.interfacesfileiobuf: + self.logger.warn('no terminal line stdin used or ') + self.logger.warn('no network interfaces file defined.') + self.warns += 1 + return + if self.interfacesfileformat == 'json': return self.read_file_json(self.interfacesfile, self.interfacesfileiobuf) diff --git a/ifupdown/policymanager.py b/ifupdown/policymanager.py index 2f9cbac..b5f3123 100644 --- a/ifupdown/policymanager.py +++ b/ifupdown/policymanager.py @@ -43,20 +43,21 @@ class policymanager(): # the defaults_policy is checked first user_files = glob.glob('/etc/network/ifupdown2/policy.d/*.json') # grab the default module files - default_files = glob.glob('/var/lib/ifupdownaddons/policy.d/*.json') + default_files = glob.glob('/var/lib/ifupdown2/policy.d/*.json') # keep an array of defaults indexed by module name self.system_policy_array = {} for filename in default_files: system_array = {} try: - fd = open(filename,'r') - system_array = json.load(fd) + with open(filename, 'r') as fd: + system_array = json.load(fd) self.logger.debug('reading %s system policy defaults config' \ % filename) except Exception, e: - self.logger.debug('could not read %s system policy defaults config' \ + self.logger.info('could not read %s system policy defaults config' \ % filename) - self.logger.debug(' exception is %s' % str(e)) + self.logger.info(' exception is %s' % str(e)) + for module in system_array.keys(): if self.system_policy_array.has_key(module): self.logger.debug('warning: overwriting system module %s from file %s' \ @@ -68,8 +69,8 @@ class policymanager(): for filename in user_files: user_array = {} try: - fd = open(filename,'r') - user_array = json.load(fd) + with open(filename, 'r') as fd: + user_array = json.load(fd) self.logger.debug('reading %s policy user defaults config' \ % filename) except Exception, e: @@ -106,25 +107,25 @@ class policymanager(): # looks for user specified value val = self.user_policy_array[module_name]['iface_defaults'][ifname][attr] return val - except: + except (TypeError, KeyError, IndexError): pass try: # failing that, there may be a user default for all intefaces val = self.user_policy_array[module_name]['defaults'][attr] return val - except: + except (TypeError, KeyError, IndexError): pass try: # failing that, look for system setting for the interface val = self.system_policy_array[module_name]['iface_defaults'][ifname][attr] return val - except: + except (TypeError, KeyError, IndexError): pass try: # failing that, look for system setting for all interfaces val = self.system_policy_array[module_name]['defaults'][attr] return val - except: + except (TypeError, KeyError, IndexError): pass # could not find any system or user default so return Non @@ -135,7 +136,7 @@ class policymanager(): get_attr_default: Addon modules must use one of two types of access methods to the default configs. In this method, we expect the default to be in - [module][attr] + [module]['defaults'][attr] We first check the user_policy_array and return that value. But if the user did not specify an override, we use the system_policy_array. @@ -143,14 +144,51 @@ class policymanager(): if (not attr or not module_name): return None # users can specify defaults to override the systemwide settings - # look for user specific interface attribute iface_defaults first + # look for user specific attribute defaults first val = None - if self.user_policy_array.get(module_name): - val = self.user_policy_array[module_name].get(attr) + try: + # looks for user specified value + val = self.user_policy_array[module_name]['defaults'][attr] + return val + except (TypeError, KeyError, IndexError): + pass + try: + # failing that, look for system setting + val = self.system_policy_array[module_name]['defaults'][attr] + return val + except (TypeError, KeyError, IndexError): + pass - if not val: - if self.system_policy_array.get(module_name): - val = self.system_policy_array[module_name].get(attr) + return val + + def get_module_globals(self,module_name=None,attr=None): + ''' + get_module_globals: Addon modules must use one of two types of access methods to + the default configs. In this method, we expect the default to be in + + [module]['module_globals'][attr] + + We first check the user_policy_array and return that value. But if + the user did not specify an override, we use the system_policy_array. + ''' + + if (not attr or not module_name): + return None + # users can specify defaults to override the systemwide settings + # look for user specific attribute defaults first + val = None + try: + # looks for user specified value + val = self.user_policy_array[module_name]['module_globals'][attr] + return val + except (TypeError, KeyError, IndexError): + pass + try: + # failing that, look for system setting + val = self.system_policy_array[module_name]['module_globals'][attr] + return val + except (TypeError, KeyError, IndexError): + pass return val diff --git a/ifupdown/scheduler.py b/ifupdown/scheduler.py index af9f28c..09ac1a6 100644 --- a/ifupdown/scheduler.py +++ b/ifupdown/scheduler.py @@ -8,6 +8,7 @@ # from statemanager import * +import ifupdown.ifupdownflags as ifupdownflags from iface import * from graph import * from collections import deque @@ -19,6 +20,7 @@ from graph import * from collections import deque from threading import * from ifupdownbase import * +from ifupdown.utils import utils from sets import Set class ifaceSchedulerFlags(): @@ -37,7 +39,15 @@ class ifaceScheduler(): _STATE_CHECK = True - _SCHED_RETVAL = True + _SCHED_STATUS = True + + @classmethod + def get_sched_status(cls): + return cls._SCHED_STATUS + + @classmethod + def set_sched_status(cls, state): + cls._SCHED_STATUS = state @classmethod def run_iface_op(cls, ifupdownobj, ifaceobj, op, cenv=None): @@ -47,7 +57,7 @@ class ifaceScheduler(): if ifupdownobj.type and ifupdownobj.type != ifaceobj.type: return - if not ifupdownobj.ADDONS_ENABLE: return + if not ifupdownobj.flags.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, @@ -66,7 +76,8 @@ class ifaceScheduler(): if op == 'query-checkcurr': # Dont check curr if the interface object was # auto generated - if (ifaceobj.priv_flags & ifupdownobj.NOCONFIG): + if (ifaceobj.priv_flags and + ifaceobj.priv_flags.NOCONFIG): continue ifupdownobj.logger.debug(msg) m.run(ifaceobj, op, query_ifaceobj, @@ -78,15 +89,15 @@ class ifaceScheduler(): except Exception, e: if not ifupdownobj.ignore_error(str(e)): err = 1 - ifupdownobj.logger.warn(str(e)) + ifupdownobj.logger.error(str(e)) # Continue with rest of the modules pass finally: if err or ifaceobj.status == ifaceStatus.ERROR: ifaceobj.set_state_n_status(ifaceState.from_str(op), ifaceStatus.ERROR) - if 'up' in op or 'down' in op: - cls._SCHED_RETVAL = False + if 'up' in op or 'down' in op or 'query-checkcurr' in op: + cls.set_sched_status(False) else: # Mark success only if the interface was not already # marked with error @@ -102,9 +113,9 @@ class ifaceScheduler(): ifupdownobj.logger.debug('%s: %s : running script %s' %(ifacename, op, mname)) try: - ifupdownobj.exec_command(mname, cmdenv=cenv) + utils.exec_command(mname, env=cenv) except Exception, e: - ifupdownobj.log_error(str(e)) + ifupdownobj.log_error('%s: %s %s' % (ifacename, op, str(e))) @classmethod def run_iface_list_ops(cls, ifupdownobj, ifaceobjs, ops): @@ -167,11 +178,14 @@ class ifaceScheduler(): if 'down' not in ops[0]: return True - if (ifupdownobj.FORCE or - not ifupdownobj.ADDONS_ENABLE or + if (ifupdownobj.flags.SCHED_SKIP_CHECK_UPPERIFACES): + return True + + if (ifupdownflags.flags.FORCE or + not ifupdownobj.flags.ADDONS_ENABLE or (not ifupdownobj.is_ifaceobj_noconfig(ifaceobj) and ifupdownobj.config.get('warn_on_ifdown', '0') == '0' and - not ifupdownobj.ALL)): + not ifupdownflags.flags.ALL)): return True ulist = ifaceobj.upperifaces @@ -188,7 +202,7 @@ class ifaceScheduler(): # return false to the caller to skip this interface for u in tmpulist: if ifupdownobj.link_exists(u): - if not ifupdownobj.ALL: + if not ifupdownflags.flags.ALL: if ifupdownobj.is_ifaceobj_noconfig(ifaceobj): ifupdownobj.logger.info('%s: skipping interface down,' %ifaceobj.name + ' upperiface %s still around ' %u) @@ -358,11 +372,16 @@ class ifaceScheduler(): uifaceobj = ifupdownobj.get_ifaceobj_first(u) if not uifaceobj: continue - has_config = not bool(uifaceobj.priv_flags - & ifupdownobj.NOCONFIG) + has_config = not (uifaceobj.priv_flags and + uifaceobj.priv_flags.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)): + # Do this always for a bridge. Note that this is + # not done for a vlan aware bridge because, + # in the vlan aware bridge case, the bridge module + # applies the bridge port configuration on the port + # when up is scheduled on the port. + or (uifaceobj.link_kind == ifaceLinkKind.BRIDGE))): nulist.append(u) upperifacenames.extend(nulist) allupperifacenames.extend(upperifacenames) @@ -390,7 +409,7 @@ class ifaceScheduler(): cls.run_iface_list_ops(ifupdownobj, ifaceobjs, ops) except Exception, e: if continueonfailure: - self.logger.warn('%s' %str(e)) + ifupdownobj.logger.warn('%s' %str(e)) @classmethod def get_sorted_iface_list(cls, ifupdownobj, ifacenames, ops, @@ -405,7 +424,7 @@ class ifaceScheduler(): ifacenames_all_sorted = graph.topological_sort_graphs_all( dependency_graph, indegrees) # if ALL was set, return all interfaces - if ifupdownobj.ALL: + if ifupdownflags.flags.ALL: return ifacenames_all_sorted # else return ifacenames passed as argument in sorted order @@ -462,7 +481,7 @@ class ifaceScheduler(): for ifacename in dependency_graph.keys(): indegrees[ifacename] = ifupdownobj.get_iface_refcnt(ifacename) - if not ifupdownobj.ALL: + if not ifupdownflags.flags.ALL: if 'up' in ops[0]: # If there is any interface that does not exist, maybe it # is a logical interface and we have to followupperifaces @@ -514,18 +533,23 @@ class ifaceScheduler(): cls.run_iface_list(ifupdownobj, run_queue, ops, parent=None, order=order, followdependents=followdependents) - if not cls._SCHED_RETVAL: - raise Exception() + if not cls.get_sched_status(): + return if (not skipupperifaces and ifupdownobj.config.get('skip_upperifaces', '0') == '0' and - ((not ifupdownobj.ALL and followdependents) or + ((not ifupdownflags.flags.ALL and followdependents) or followupperifaces) and 'up' in ops[0]): # If user had given a set of interfaces to bring up # try and execute 'up' on the upperifaces ifupdownobj.logger.info('running upperifaces (parent interfaces) ' + 'if available ..') - cls._STATE_CHECK = False - cls.run_upperifaces(ifupdownobj, ifacenames, ops) - cls._STATE_CHECK = True + try: + cls._STATE_CHECK = False + cls.run_upperifaces(ifupdownobj, ifacenames, ops) + cls._STATE_CHECK = True + finally: + # upperiface bringup is best effort, so dont propagate errors + # reset scheduler status to True + cls.set_sched_status(True) diff --git a/ifupdown/statemanager.py b/ifupdown/statemanager.py index ef9ca7f..9eee24f 100644 --- a/ifupdown/statemanager.py +++ b/ifupdown/statemanager.py @@ -9,6 +9,7 @@ import cPickle from collections import OrderedDict import logging +import exceptions import os from iface import * @@ -58,6 +59,12 @@ class stateManager(): state_filename = 'ifstatenew' """name of the satefile """ + state_rundir = '/run/network/' + """name of the state run dir """ + + state_runlockfile = 'ifstatelock' + """name of the state run lock file """ + def __init__(self): """ Initializes statemanager internal state @@ -68,6 +75,8 @@ class stateManager(): self.__class__.__name__) if not os.path.exists(self.state_dir): os.mkdir(self.state_dir) + if not os.path.exists(self.state_rundir): + os.mkdir(self.state_rundir) self.state_file = self.state_dir + self.state_filename def save_ifaceobj(self, ifaceobj): @@ -146,6 +155,7 @@ class stateManager(): self.logger.debug('saving state ..') for ifaceobjs in self.ifaceobjdict.values(): [pickling.save_obj(f, i) for i in ifaceobjs] + open('%s/%s' %(self.state_rundir, self.state_runlockfile), 'w').close() except: raise @@ -168,9 +178,11 @@ class stateManager(): for i in ifacenames: ifaceobj = self.ifaces.get(i) if ifaceobj is None: - raise ifaceNotFoundError('ifname %s' + raise exceptions.ifaceNotFoundError('ifname %s' %i + ' not found') ifaceobj.dump(self.logger) else: for ifacename, ifaceobjs in self.ifaceobjdict.items(): [i.dump(self.logger) for i in ifaceobjs] + +statemanager_api = stateManager() diff --git a/ifupdown/utils.py b/ifupdown/utils.py index caf6583..6aeba0b 100644 --- a/ifupdown/utils.py +++ b/ifupdown/utils.py @@ -6,11 +6,89 @@ # utils -- # helper class # + import os -import fcntl import re +import shlex +import fcntl +import signal +import logging +import subprocess +import ifupdownflags + +from functools import partial + +def signal_handler_f(ps, sig, frame): + if ps: + ps.send_signal(sig) + if sig == signal.SIGINT: + raise KeyboardInterrupt class utils(): + logger = logging.getLogger('ifupdown') + DEVNULL = open(os.devnull, 'w') + + _string_values = { + "on": True, + "yes": True, + "1": True, + "off": False, + "no": False, + "0": False, + } + + _binary_bool = { + True: "1", + False: "0", + } + + _yesno_bool = { + True: 'yes', + False: 'no' + } + + _onoff_bool = { + 'yes': 'on', + 'no': 'off' + } + + @staticmethod + def get_onoff_bool(value): + if value in utils._onoff_bool: + return utils._onoff_bool[value] + return value + + @staticmethod + def get_boolean_from_string(value): + if value in utils._string_values: + return utils._string_values[value] + return False + + @staticmethod + def get_yesno_boolean(bool): + return utils._yesno_bool[bool] + + @staticmethod + def boolean_support_binary(value): + return utils._binary_bool[utils.get_boolean_from_string(value)] + + @staticmethod + def is_binary_bool(value): + return value == '0' or value == '1' + + @staticmethod + def support_yesno_attrs(attrsdict, attrslist, ifaceobj=None): + if ifaceobj: + for attr in attrslist: + value = ifaceobj.get_attr_value_first(attr) + if value and not utils.is_binary_bool(value): + if attr in attrsdict: + bool = utils.get_boolean_from_string(attrsdict[attr]) + attrsdict[attr] = utils.get_yesno_boolean(bool) + else: + for attr in attrslist: + if attr in attrsdict: + attrsdict[attr] = utils.boolean_support_binary(attrsdict[attr]) @classmethod def importName(cls, modulename, name): @@ -26,6 +104,7 @@ class utils(): try: fp = os.open(lockfile, os.O_CREAT | os.O_TRUNC | os.O_WRONLY) fcntl.flock(fp, fcntl.LOCK_EX | fcntl.LOCK_NB) + fcntl.fcntl(fp, fcntl.F_SETFD, fcntl.FD_CLOEXEC) except IOError: return False return True @@ -49,4 +128,116 @@ class utils(): ifacenames.append('%s-%d' %(iface_range[0], i)) return ifacenames + @classmethod + def check_ifname_size_invalid(cls, name=''): + """ IFNAMSIZ in include/linux/if.h is 16 so we check this """ + IFNAMSIZ = 16 + if len(name) > IFNAMSIZ - 1: + return True + else: + return False + @classmethod + def enable_subprocess_signal_forwarding(cls, ps, sig): + signal.signal(sig, partial(signal_handler_f, ps)) + + @classmethod + def disable_subprocess_signal_forwarding(cls, sig): + signal.signal(sig, signal.SIG_DFL) + + @classmethod + def _log_command_exec(cls, cmd, stdin): + if stdin: + cls.logger.info('executing %s [%s]' % (cmd, stdin)) + else: + cls.logger.info('executing %s' % cmd) + + @classmethod + def _format_error(cls, cmd, cmd_returncode, cmd_output, stdin): + if type(cmd) is list: + cmd = ' '.join(cmd) + if stdin: + cmd = '%s [%s]' % (cmd, stdin) + if cmd_output: + return 'cmd \'%s\' failed: returned %d (%s)' % \ + (cmd, cmd_returncode, cmd_output) + else: + return 'cmd \'%s\' failed: returned %d' % (cmd, cmd_returncode) + + @classmethod + def _execute_subprocess(cls, cmd, + env=None, + shell=False, + close_fds=False, + stdout=True, + stdin=None, + stderr=subprocess.STDOUT): + """ + exec's commands using subprocess Popen + Args: + cmd, should be shlex.split if not shell + returns: output + + Note: close_fds=True is affecting performance (2~3 times slower) + """ + if ifupdownflags.flags.DRYRUN: + return '' + + cmd_output = None + try: + ch = subprocess.Popen(cmd, + env=env, + shell=shell, + close_fds=close_fds, + stdin=subprocess.PIPE if stdin else None, + stdout=subprocess.PIPE if stdout else cls.DEVNULL, + stderr=stderr) + utils.enable_subprocess_signal_forwarding(ch, signal.SIGINT) + if stdout or stdin: + cmd_output = ch.communicate(input=stdin)[0] + cmd_returncode = ch.wait() + except Exception as e: + raise Exception('cmd \'%s\' failed (%s)' % (' '.join(cmd), str(e))) + finally: + utils.disable_subprocess_signal_forwarding(signal.SIGINT) + if cmd_returncode != 0: + raise Exception(cls._format_error(cmd, + cmd_returncode, + cmd_output, + stdin)) + return cmd_output + + @classmethod + def exec_user_command(cls, cmd, close_fds=False, stdout=True, + stdin=None, stderr=subprocess.STDOUT): + cls._log_command_exec(cmd, stdin) + return cls._execute_subprocess(cmd, + shell=True, + close_fds=close_fds, + stdout=stdout, + stdin=stdin, + stderr=stderr) + + @classmethod + def exec_command(cls, cmd, env=None, close_fds=False, stdout=True, + stdin=None, stderr=subprocess.STDOUT): + cls._log_command_exec(cmd, stdin) + return cls._execute_subprocess(shlex.split(cmd), + env=env, + close_fds=close_fds, + stdout=stdout, + stdin=stdin, + stderr=stderr) + + @classmethod + def exec_commandl(cls, cmdl, env=None, close_fds=False, stdout=True, + stdin=None, stderr=subprocess.STDOUT): + cls._log_command_exec(' '.join(cmdl), stdin) + return cls._execute_subprocess(cmdl, + env=env, + close_fds=close_fds, + stdout=stdout, + stdin=stdin, + stderr=stderr) + +fcntl.fcntl(utils.DEVNULL, fcntl.F_SETFD, fcntl.FD_CLOEXEC) diff --git a/ifupdownaddons/bondutil.py b/ifupdownaddons/bondutil.py new file mode 100644 index 0000000..366adfa --- /dev/null +++ b/ifupdownaddons/bondutil.py @@ -0,0 +1,380 @@ +#!/usr/bin/python +# +# Copyright 2014 Cumulus Networks, Inc. All rights reserved. +# Author: Roopa Prabhu, roopa@cumulusnetworks.com +# + +import os +import re +import ifupdown.ifupdownflags as ifupdownflags +from ifupdown.utils import utils +from ifupdown.iface import * +from utilsbase import * +from iproute2 import * +from cache import * + +class bondutil(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 ifupdownflags.flags.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_actor_sys_prio'], + self.read_file_oneline('/sys/class/net/%s/bonding/ad_actor_sys_prio' + %bondname)) + linkCache.set_attr([bondname, 'linkinfo', 'ad_actor_system'], + self.read_file_oneline('/sys/class/net/%s/bonding/ad_actor_system' + %bondname)) + linkCache.set_attr([bondname, 'linkinfo', 'lacp_bypass'], + self.read_file_oneline('/sys/class/net/%s/bonding/lacp_bypass' + %bondname).split()[1]) + 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']) + 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): + if not refresh: + 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 ifupdownflags.flags.DRYRUN: + return None + if ifupdownflags.flags.CACHE: + if not bondutil._cache_fill_done: + self._bond_linkinfo_fill_all() + bondutil._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 ifupdownflags.flags.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 ifupdownflags.flags.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 ifupdownflags.flags.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: + if ((attrname not in ['lacp_rate', + 'lacp_bypass']) or + self._cache_check([bondname, 'linkinfo', 'mode'], '802.3ad', + True)): + self.write_file('/sys/class/net/%s/bonding/%s' + %(bondname, attrname), attrval) + except Exception, e: + if ifupdownflags.flags.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_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_bypass_allow(self, bondname, allow, prehook=None, posthook=None): + if (self._cache_check([bondname, 'linkinfo', 'lacp_bypass'], allow)): + return + if prehook: + prehook(bondname) + try: + self.write_file('/sys/class/net/%s' %bondname + + '/bonding/lacp_bypass', allow) + except: + raise + finally: + if posthook: + posthook(bondname) + self._cache_update([bondname, 'linkinfo', + 'lacp_bypass'], allow) + + def get_lacp_bypass_allow(self, bondname): + return self._cache_get([bondname, 'linkinfo', 'lacp_bypass']) + + 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 get_ad_actor_system(self, bondname): + return self._cache_get([bondname, 'linkinfo', 'ad_actor_system']) + + def get_ad_actor_sys_prio(self, bondname): + return self._cache_get([bondname, 'linkinfo', 'ad_actor_sys_prio']) + + def get_num_unsol_na(self, bondname): + return self._cache_get([bondname, 'linkinfo', 'num_unsol_na']) + + def get_num_grat_arp(self, bondname): + return self._cache_get([bondname, 'linkinfo', 'num_grat_arp']) + + 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: + with open(sysfs_bond_path, 'r') as f: + slaves = f.readline().strip().split() + 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 ifupdownflags.flags.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 utils.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() + 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/ifupdownaddons/bridgeutils.py b/ifupdownaddons/bridgeutils.py index 73f28b1..6c6e091 100644 --- a/ifupdownaddons/bridgeutils.py +++ b/ifupdownaddons/bridgeutils.py @@ -9,6 +9,8 @@ from utilsbase import * import os import re import logging +from ifupdown.utils import utils +import ifupdown.ifupdownflags as ifupdownflags from cache import * class brctl(utilsBase): @@ -19,7 +21,7 @@ class brctl(utilsBase): def __init__(self, *args, **kargs): utilsBase.__init__(self, *args, **kargs) - if self.CACHE and not brctl._cache_fill_done: + if ifupdownflags.flags.CACHE and not brctl._cache_fill_done: self._bridge_fill() brctl._cache_fill_done = True @@ -62,7 +64,7 @@ class brctl(utilsBase): battrs = {} bports = {} - brout = self.exec_command('/sbin/brctl showstp %s' %bridgename) + brout = utils.exec_command('/sbin/brctl showstp %s' % bridgename) chunks = re.split(r'\n\n', brout, maxsplit=0, flags=re.MULTILINE) try: @@ -77,6 +79,12 @@ class brctl(utilsBase): battrs['fd'] = broutlines[6].split( 'bridge forward delay')[1].strip( ).replace('.00', '') + battrs['ageing'] = broutlines[7].split( + 'ageing time')[1].strip().replace('.00', '') + battrs['mcrouter'] = broutlines[12].split( + 'mc router')[1].strip().split('\t\t\t')[0] + battrs['bridgeprio'] = self.read_file_oneline( + '/sys/class/net/%s/bridge/priority' %bridgename) battrs.update(self._bridge_get_mcattrs_from_sysfs(bridgename)) # XXX: comment this out until mc attributes become available @@ -89,11 +97,10 @@ class brctl(utilsBase): ##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)) + self.logger.warn('%s: error while processing bridge attributes: %s' % (bridgename, 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] == ' ': @@ -106,16 +113,16 @@ class brctl(utilsBase): 'path cost')[1].strip() bportattrs['fdelay'] = bplines[4].split( 'forward delay timer')[1].strip() - bportattrs['mcrouter'] = self.read_file_oneline( + bportattrs['portmcrouter'] = self.read_file_oneline( '/sys/class/net/%s/brport/multicast_router' %pname) - bportattrs['mcfl'] = self.read_file_oneline( + bportattrs['portmcfl'] = self.read_file_oneline( '/sys/class/net/%s/brport/multicast_fast_leave' %pname) - + bportattrs['portprio'] = self.read_file_oneline( + '/sys/class/net/%s/brport/priority' %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 + self.logger.warn('%s: error while processing bridge attributes: %s' % (bridgename, str(e))) bports[pname] = bportattrs linkCache.update_attrdict([bridgename, 'linkinfo', 'ports'], bports) @@ -127,9 +134,9 @@ class brctl(utilsBase): except: pass if not bridgename: - brctlout = self.exec_command('/sbin/brctl show') + brctlout = utils.exec_command('/sbin/brctl show') else: - brctlout = self.exec_command('/sbin/brctl show ' + bridgename) + brctlout = utils.exec_command('/sbin/brctl show %s' % bridgename) if not brctlout: return @@ -147,9 +154,9 @@ class brctl(utilsBase): def _cache_get(self, attrlist, refresh=False): try: - if self.DRYRUN: + if ifupdownflags.flags.DRYRUN: return None - if self.CACHE: + if ifupdownflags.flags.CACHE: if not self._cache_fill_done: self._bridge_fill() self._cache_fill_done = True @@ -176,33 +183,33 @@ class brctl(utilsBase): return False def _cache_update(self, attrlist, value): - if self.DRYRUN: return + if ifupdownflags.flags.DRYRUN: return try: linkCache.add_attr(attrlist, value) except: pass def _cache_delete(self, attrlist): - if self.DRYRUN: return + if ifupdownflags.flags.DRYRUN: return try: linkCache.del_attr(attrlist) except: pass def _cache_invalidate(self): - if self.DRYRUN: return + if ifupdownflags.flags.DRYRUN: return linkCache.invalidate() def create_bridge(self, bridgename): if self.bridge_exists(bridgename): return - self.exec_command('/sbin/brctl addbr %s' %bridgename) + utils.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) + utils.exec_command('/sbin/brctl delbr %s' % bridgename) self._cache_invalidate() def add_bridge_port(self, bridgename, bridgeportname): @@ -210,8 +217,8 @@ class brctl(utilsBase): ports = self._cache_get([bridgename, 'linkinfo', 'ports']) if ports and ports.get(bridgeportname): return - self.exec_command('/sbin/brctl addif ' + bridgename + ' ' + - bridgeportname) + utils.exec_command('/sbin/brctl addif %s %s' % + (bridgename, bridgeportname)) self._cache_update([bridgename, 'linkinfo', 'ports', bridgeportname], {}) @@ -220,8 +227,8 @@ class brctl(utilsBase): ports = self._cache_get([bridgename, 'linkinfo', 'ports']) if not ports or not ports.get(bridgeportname): return - self.exec_command('/sbin/brctl delif ' + bridgename + ' ' + - bridgeportname) + utils.exec_command('/sbin/brctl delif %s %s' % + (bridgename, bridgeportname)) self._cache_delete([bridgename, 'linkinfo', 'ports', 'bridgeportname']) @@ -230,20 +237,23 @@ class brctl(utilsBase): 'ports', bridgeportname]) if portattrs == None: portattrs = {} for k, v in attrdict.iteritems(): - if self.CACHE: + if ifupdownflags.flags.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)) + utils.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)) + utils.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(): @@ -252,8 +262,8 @@ class brctl(utilsBase): if self._cache_check([bridgename, 'linkinfo', k], v): continue try: - self.exec_command('/sbin/brctl set%s %s %s' - %(k, bridgename, v)) + cmd = '/sbin/brctl set%s %s %s' % (k, bridgename, v) + utils.exec_command(cmd) except Exception, e: self.logger.warn('%s: %s' %(bridgename, str(e))) pass @@ -261,8 +271,8 @@ class brctl(utilsBase): 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)) + utils.exec_command('/sbin/brctl set%s %s %s' % + (attrname, bridgename, attrval)) def get_bridge_attrs(self, bridgename): return self._cache_get([bridgename, 'linkinfo']) @@ -276,7 +286,7 @@ class brctl(utilsBase): bridgeportname, attrname]) def set_stp(self, bridge, stp_state): - self.exec_command('/sbin/brctl stp ' + bridge + ' ' + stp_state) + utils.exec_command('/sbin/brctl stp %s %s' % (bridge, stp_state)) def get_stp(self, bridge): sysfs_stpstate = '/sys/class/net/%s/bridge/stp_state' %bridge @@ -308,22 +318,21 @@ class brctl(utilsBase): return preprocess_func(value) def set_ageing(self, bridge, ageing): - self.exec_command('/sbin/brctl setageing ' + bridge + ' ' + ageing) + utils.exec_command('/sbin/brctl setageing %s %s' % (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 set_bridgeprio(self, bridge, prio): + utils.exec_command('/sbin/brctl setbridgeprio %s %s' % (bridge, prio)) 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) + utils.exec_command('/sbin/brctl setfd %s %s' % (bridge, fd)) def get_fd(self, bridge): return self.read_value_from_sysfs( @@ -335,51 +344,51 @@ class brctl(utilsBase): raise Exception('set_gcint not implemented') def set_hello(self, bridge, hello): - self.exec_command('/sbin/brctl sethello ' + bridge + ' ' + hello) + utils.exec_command('/sbin/brctl sethello %s %s' % (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) + utils.exec_command('/sbin/brctl setmaxage %s %s' % (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) + utils.exec_command('/sbin/brctl setpathcost %s %s %s' % + (bridge, port, 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) + utils.exec_command('/sbin/brctl setportprio %s %s %s' % + (bridge, port, 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) + utils.exec_command('/sbin/brctl sethashmax %s %s' % (bridge, 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) + utils.exec_command('/sbin/brctl sethashel %s %s' % (bridge, 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) + utils.exec_command('/sbin/brctl setmclmc %s %s' % (bridge, mclmc)) def get_mclmc(self, bridge): return self.read_file_oneline( @@ -387,24 +396,21 @@ class brctl(utilsBase): %bridge) def set_mcrouter(self, bridge, mcrouter): - self.exec_command('/sbin/brctl setmcrouter %s' %bridge + - ' %s' %mcrouter) + utils.exec_command('/sbin/brctl setmcrouter %s %s' % (bridge, 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) + utils.exec_command('/sbin/brctl setmcsnoop %s %s' % (bridge, 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) + utils.exec_command('/sbin/brctl setmcsqc %s %s' % (bridge, mcsqc)) def get_mcsqc(self, bridge): return self.read_file_oneline( @@ -412,8 +418,8 @@ class brctl(utilsBase): %bridge) def set_mcqifaddr(self, bridge, mcqifaddr): - self.exec_command('/sbin/brctl setmcqifaddr %s' %bridge + - ' %s' %mcqifaddr) + utils.exec_command('/sbin/brctl setmcqifaddr %s %s' % + (bridge, mcqifaddr)) def get_mcqifaddr(self, bridge): return self.read_file_oneline( @@ -421,8 +427,8 @@ class brctl(utilsBase): %bridge) def set_mcquerier(self, bridge, mcquerier): - self.exec_command('/sbin/brctl setmcquerier %s' %bridge + - ' %s' %mcquerier) + utils.exec_command('/sbin/brctl setmcquerier %s %s' % + (bridge, mcquerier)) def get_mcquerier(self, bridge): return self.read_file_oneline( @@ -442,15 +448,15 @@ class brctl(utilsBase): self.logger.warn('mcqv4src \'%s\' invalid IPv4 address' %mcquerier) return - self.exec_command('/sbin/brctl setmcqv4src %s' %bridge + - ' %d %s' %(vlan, mcquerier)) + utils.exec_command('/sbin/brctl setmcqv4src %s %d %s' % + (bridge, vlan, mcquerier)) def del_mcqv4src(self, bridge, vlan): - self.exec_command('/sbin/brctl delmcqv4src %s %d' %(bridge, vlan)) + utils.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) + mcqout = utils.exec_command('/sbin/brctl showmcqv4src %s' % bridge) if not mcqout: return None mcqlines = mcqout.splitlines() for l in mcqlines[1:]: @@ -464,8 +470,7 @@ class brctl(utilsBase): return mcqv4src def set_mclmi(self, bridge, mclmi): - self.exec_command('/sbin/brctl setmclmi %s' %bridge + - ' %s' %mclmi) + utils.exec_command('/sbin/brctl setmclmi %s %s' % (bridge, mclmi)) def get_mclmi(self, bridge): return self.read_file_oneline( @@ -473,8 +478,7 @@ class brctl(utilsBase): %bridge) def set_mcmi(self, bridge, mcmi): - self.exec_command('/sbin/brctl setmcmi %s' %bridge + - ' %s' %mcmi) + utils.exec_command('/sbin/brctl setmcmi %s %s' % (bridge, mcmi)) def get_mcmi(self, bridge): return self.read_file_oneline( diff --git a/ifupdownaddons/cache.py b/ifupdownaddons/cache.py index 61773b5..35da9f6 100644 --- a/ifupdownaddons/cache.py +++ b/ifupdownaddons/cache.py @@ -5,7 +5,26 @@ # import pprint -from collections import OrderedDict + + +class MSTPAttrsCache(): + bridges = {} + + @classmethod + def get(cls, bridgename, default=None): + if bridgename in MSTPAttrsCache.bridges: + return MSTPAttrsCache.bridges[bridgename] + else: + return default + + @classmethod + def set(cls, bridgename, attrs): + MSTPAttrsCache.bridges[bridgename] = attrs + + @classmethod + def invalidate(cls): + MSTPAttrsCache.bridges = {} + class linkCache(): """ This class contains methods and instance variables to cache @@ -23,6 +42,8 @@ class linkCache(): : { } """ links = {} + vrfs = {} + @classmethod def get_attr(cls, mapList): return reduce(lambda d, k: d[k], mapList, linkCache.links) @@ -85,6 +106,6 @@ class linkCache(): @classmethod def dump_link(cls, linkname): - print 'Dumping link %s' %linkname + print 'Dumping link %s' % linkname pp = pprint.PrettyPrinter(indent=4) pp.pprint(cls.links.get(linkname)) diff --git a/ifupdownaddons/dhclient.py b/ifupdownaddons/dhclient.py index 811ff40..81b45f4 100644 --- a/ifupdownaddons/dhclient.py +++ b/ifupdownaddons/dhclient.py @@ -4,11 +4,10 @@ # Author: Roopa Prabhu, roopa@cumulusnetworks.com # +from ifupdown.utils import utils 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 @@ -29,7 +28,18 @@ class dhclient(utilsBase): def is_running6(self, ifacename): return self._pid_exists('/run/dhclient6.%s.pid' %ifacename) - def stop(self, ifacename): + def _run_dhclient_cmd(self, cmd, cmd_prefix=None): + if not cmd_prefix: + cmd_aslist = [] + else: + cmd_aslist = cmd_prefix.split() + if cmd_aslist: + cmd_aslist.extend(cmd) + else: + cmd_aslist = cmd + utils.exec_commandl(cmd_aslist, stdout=None, stderr=None) + + def stop(self, ifacename, cmd_prefix=None): if os.path.exists('/sbin/dhclient3'): cmd = ['/sbin/dhclient3', '-x', '-pf', '/run/dhclient.%s.pid' %ifacename, '-lf', @@ -40,21 +50,24 @@ class dhclient(utilsBase): '/run/dhclient.%s.pid' %ifacename, '-lf', '/var/lib/dhcp/dhclient.%s.leases' %ifacename, '%s' %ifacename] - self.subprocess_check_call(cmd) + self._run_dhclient_cmd(cmd, cmd_prefix) - def start(self, ifacename): + def start(self, ifacename, wait=True, cmd_prefix=None): 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, + cmd = ['/sbin/dhclient', '-pf', + '/run/dhclient.%s.pid' %ifacename, '-lf', + '/var/lib/dhcp/dhclient.%s.leases' %ifacename, '%s' %ifacename] - self.subprocess_check_call(cmd) + if not wait: + cmd.append('-nw') + self._run_dhclient_cmd(cmd, cmd_prefix) - def release(self, ifacename): + def release(self, ifacename, cmd_prefix=None): if os.path.exists('/sbin/dhclient3'): cmd = ['/sbin/dhclient3', '-r', '-pf', '/run/dhclient.%s.pid' %ifacename, '-lf', @@ -65,22 +78,27 @@ class dhclient(utilsBase): '/run/dhclient.%s.pid' %ifacename, '-lf', '/var/lib/dhcp/dhclient.%s.leases' %ifacename, '%s' %ifacename] - self.subprocess_check_call(cmd) + self._run_dhclient_cmd(cmd, cmd_prefix) - def start6(self, ifacename): - self.subprocess_check_call(['dhclient', '-6', '-pf', + def start6(self, ifacename, wait=True, cmd_prefix=None): + cmd = ['/sbin/dhclient', '-6', '-pf', '/run/dhclient6.%s.pid' %ifacename, '-lf', '/var/lib/dhcp/dhclient.%s.leases ' %ifacename, - '%s' %ifacename]) + '%s' %ifacename] + if not wait: + cmd.append('-nw') + self._run_dhclient_cmd(cmd, cmd_prefix) - 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 stop6(self, ifacename, cmd_prefix=None): + cmd = ['/sbin/dhclient', '-6', '-x', '-pf', + '/run/dhclient.%s.pid' %ifacename, '-lf', + '/var/lib/dhcp/dhclient.%s.leases ' %ifacename, + '%s' %ifacename] + self._run_dhclient_cmd(cmd, cmd_prefix) - 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]) + def release6(self, ifacename, cmd_prefix=None): + cmd = ['/sbin/dhclient', '-6', '-r', '-pf', + '/run/dhclient6.%s.pid' %ifacename, '-lf', + '/var/lib/dhcp/dhclient6.%s.leases' %ifacename, + '%s' %ifacename] + self._run_dhclient_cmd(cmd, cmd_prefix) diff --git a/ifupdownaddons/iproute2.py b/ifupdownaddons/iproute2.py index d09b1a6..b1f4b6d 100644 --- a/ifupdownaddons/iproute2.py +++ b/ifupdownaddons/iproute2.py @@ -5,9 +5,17 @@ # import os +import glob +import shlex +import signal +import subprocess + +from ifupdown.utils import utils from collections import OrderedDict from utilsbase import * +from systemutils import * from cache import * +import ifupdown.ifupdownflags as ifupdownflags VXLAN_UDP_PORT = 4789 @@ -22,11 +30,28 @@ class iproute2(utilsBase): def __init__(self, *args, **kargs): utilsBase.__init__(self, *args, **kargs) - if self.CACHE and not iproute2._cache_fill_done: + if ifupdownflags.flags.CACHE: + self._fill_cache() + + def _fill_cache(self): + if not iproute2._cache_fill_done: self._link_fill() self._addr_fill() iproute2._cache_fill_done = True - + return True + return False + + def _get_vland_id(self, citems, i, warn): + try: + sub = citems[i:] + index = sub.index('id') + int(sub[index + 1]) + return sub[index + 1] + except: + if warn: + raise Exception('invalid use of \'vlan\' keyword') + return None + def _link_fill(self, ifacename=None, refresh=False): """ fills cache with link information @@ -34,6 +59,7 @@ class iproute2(utilsBase): fill cache for all interfaces in the system """ + warn = True linkout = {} if iproute2._cache_fill_done and not refresh: return try: @@ -62,41 +88,64 @@ class iproute2(utilsBase): 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' : [], - 'remote' : [], - 'ageing' : citems[i+2], - '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' - # get vxlan peer nodes - peers = self.get_vxlan_peers(ifname) - if peers: - vattrs['remote'] = peers - linkattrs['linkinfo'] = vattrs - break + try: + 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': + vlanid = self._get_vland_id(citems, i, warn) + if vlanid: + linkattrs['linkinfo'] = {'vlanid': vlanid} + linkattrs['kind'] = 'vlan' + elif citems[i] == 'dummy': + linkattrs['kind'] = 'dummy' + elif citems[i] == 'vxlan' and citems[i + 1] == 'id': + linkattrs['kind'] = 'vxlan' + vattrs = {'vxlanid': citems[i + 2], + 'svcnode': None, + 'remote': [], + 'ageing': citems[i + 2], + 'learning': 'on'} + for j in range(i + 2, len(citems)): + if citems[j] == 'local': + vattrs['local'] = citems[j + 1] + elif citems[j] == 'remote': + vattrs['svcnode'] = citems[j + 1] + elif citems[j] == 'ageing': + vattrs['ageing'] = citems[j + 1] + elif citems[j] == 'nolearning': + vattrs['learning'] = 'off' + # get vxlan peer nodes + peers = self.get_vxlan_peers(ifname, vattrs['svcnode']) + if peers: + vattrs['remote'] = peers + linkattrs['linkinfo'] = vattrs + break + elif citems[i] == 'vrf' and citems[i + 1] == 'table': + vattrs = {'table': citems[i + 2]} + linkattrs['linkinfo'] = vattrs + linkattrs['kind'] = 'vrf' + linkCache.vrfs[ifname] = vattrs + break + elif citems[i] == 'vrf_slave': + linkattrs['kind'] = 'vrf_slave' + break + except Exception as e: + if warn: + self.logger.debug('%s: parsing error: id, mtu, state, link/ether, vlan, dummy, vxlan, local, remote, ageing, nolearning, vrf, table, vrf_slave are reserved keywords: %s' % (ifname, str(e))) + warn = False #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): + def _addr_filter(self, ifname, addr, scope=None): default_addrs = ['127.0.0.1/8', '::1/128' , '0.0.0.0'] - if addr in default_addrs: + if ifname == 'lo' and addr in default_addrs: return True if scope and scope == 'link': return True @@ -108,13 +157,13 @@ class iproute2(utilsBase): if ifacename argument given, fill cache for ifacename, else fill cache for all interfaces in the system """ - linkout = {} - if iproute2._cache_fill_done: return + if iproute2._cache_fill_done and not refresh: return + try: # Check if ifacename is already full, in which case, return - if ifacename: - linkCache.get_attr([ifacename, 'addrs']) + if ifacename and not refresh: + linkCache.get_attr([ifacename, 'addrs']) return except: pass @@ -128,41 +177,38 @@ class iproute2(utilsBase): 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: + if not linkout.get(ifname): linkattrs = {} linkattrs['addrs'] = OrderedDict({}) try: linkout[ifname].update(linkattrs) except KeyError: linkout[ifname] = linkattrs - + if citems[2] == 'inet': + if self._addr_filter(ifname, 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(ifname, 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 [linkCache.update_attrdict([ifname], linkattrs) for ifname, linkattrs in linkout.items()] def _cache_get(self, type, attrlist, refresh=False): try: - if self.DRYRUN: + if ifupdownflags.flags.DRYRUN: return False - if self.CACHE: - if not iproute2._cache_fill_done: - self._link_fill() - self._addr_fill() - iproute2._cache_fill_done = True + if ifupdownflags.flags.CACHE: + if self._fill_cache(): + # if we filled the cache, return new data return linkCache.get_attr(attrlist) if not refresh: return linkCache.get_attr(attrlist) @@ -192,14 +238,14 @@ class iproute2(utilsBase): return False def _cache_update(self, attrlist, value): - if self.DRYRUN: return + if ifupdownflags.flags.DRYRUN: return try: linkCache.add_attr(attrlist, value) except: pass def _cache_delete(self, attrlist): - if self.DRYRUN: return + if ifupdownflags.flags.DRYRUN: return try: linkCache.del_attr(attrlist) except: @@ -207,6 +253,7 @@ class iproute2(utilsBase): def _cache_invalidate(self): linkCache.invalidate() + iproute2._cache_fill_done = False def batch_start(self): self.ipbatcbuf = '' @@ -224,11 +271,13 @@ class iproute2(utilsBase): def batch_commit(self): if not self.ipbatchbuf: + self.ipbatchbuf = '' + self.ipbatch = False + self.ipbatch_pause = False return try: - self.exec_command_talk_stdin('ip -force -batch -', - stdinbuf=self.ipbatchbuf) - except Exception: + utils.exec_command('ip -force -batch -', stdin=self.ipbatchbuf) + except: raise finally: self.ipbatchbuf = '' @@ -239,17 +288,17 @@ class iproute2(utilsBase): if ifacename: if not self.link_exists(ifacename): return - return self.exec_commandl(['ip','-o', 'addr', 'show', 'dev', - '%s' %ifacename]) + return utils.exec_commandl(['ip', '-o', 'addr', 'show', 'dev', + ifacename]) else: - return self.exec_commandl(['ip', '-o', 'addr', 'show']) + return utils.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]) + return utils.exec_commandl(['ip', '-o', '-d', 'link', 'show', 'dev', + ifacename]) else: - return self.exec_commandl(['ip', '-o', '-d', 'link', 'show']) + return utils.exec_commandl(['ip', '-o', '-d', 'link', 'show']) def addr_add(self, ifacename, address, broadcast=None, peer=None, scope=None, preferred_lifetime=None): @@ -268,7 +317,7 @@ class iproute2(utilsBase): if self.ipbatch and not self.ipbatch_pause: self.add_to_batch(cmd) else: - self.exec_command('ip ' + cmd) + utils.exec_command('ip %s' % cmd) self._cache_update([ifacename, 'addrs', address], {}) def addr_del(self, ifacename, address, broadcast=None, @@ -286,7 +335,7 @@ class iproute2(utilsBase): if scope: cmd += 'scope %s' %scope cmd += ' dev %s' %ifacename - self.exec_command('ip ' + cmd) + utils.exec_command('ip %s' % cmd) self._cache_delete([ifacename, 'addrs', address]) def addr_flush(self, ifacename): @@ -294,7 +343,7 @@ class iproute2(utilsBase): if self.ipbatch and not self.ipbatch_pause: self.add_to_batch(cmd) else: - self.exec_command('ip ' + cmd) + utils.exec_command('ip %s' % cmd) self._cache_delete([ifacename, 'addrs']) def del_addr_all(self, ifacename, skip_addrs=[]): @@ -309,8 +358,9 @@ class iproute2(utilsBase): # ignore errors pass - def addr_get(self, ifacename, details=True): - addrs = self._cache_get('addr', [ifacename, 'addrs']) + def addr_get(self, ifacename, details=True, refresh=False): + addrs = self._cache_get('addr', [ifacename, 'addrs'], + refresh=refresh) if not addrs: return None if details: @@ -350,7 +400,7 @@ class iproute2(utilsBase): if self.ipbatch: self.add_to_batch(cmd) else: - self.exec_command('ip ' + cmd) + utils.exec_command('ip %s' % cmd) def link_up(self, ifacename): self._link_set_ifflag(ifacename, 'UP') @@ -358,18 +408,21 @@ class iproute2(utilsBase): def link_down(self, ifacename): self._link_set_ifflag(ifacename, 'DOWN') - def link_set(self, ifacename, key, value=None, force=False): + def link_set(self, ifacename, key, value=None, force=False, type=None): 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) + cmd = 'link set dev %s' %ifacename + if type: + cmd += ' type %s' %type + cmd += ' %s' %key if value: cmd += ' %s' %value if self.ipbatch: self.add_to_batch(cmd) else: - self.exec_command('ip ' + cmd) + utils.exec_command('ip %s' % cmd) if key not in ['master', 'nomaster']: self._cache_update([ifacename, key], value) @@ -382,13 +435,21 @@ class iproute2(utilsBase): if self.ipbatch: self.add_to_batch(cmd) else: - self.exec_command('ip ' + cmd) + utils.exec_command('ip %s' % cmd) self.link_up(ifacename) self._cache_update([ifacename, 'hwaddress'], hwaddress) + def link_set_mtu(self, ifacename, mtu): + if ifupdownflags.flags.DRYRUN: + return True + if not mtu or not ifacename: return + with open('/sys/class/net/%s/mtu' % ifacename, 'w') as f: + f.write(mtu) + self._cache_update([ifacename, 'mtu'], mtu) + def link_set_alias(self, ifacename, alias): - self.exec_commandl(['ip', 'link', 'set', 'dev', - ifacename, 'alias', alias]) + utils.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' @@ -405,51 +466,56 @@ class iproute2(utilsBase): def link_get_status(self, ifacename): return self._cache_get('link', [ifacename, 'ifflag'], refresh=True) - def route_add_gateway(self, ifacename, gateway, metric=None): + def route_add_gateway(self, ifacename, gateway, vrf=None, metric=None): if not gateway: return - cmd = 'ip route add default via %s' %gateway + if not vrf: + cmd = 'ip route add default via %s' %gateway + else: + cmd = 'ip route add table %s default via %s' %(vrf, gateway) # Add metric if metric: cmd += 'metric %s' %metric cmd += ' dev %s' %ifacename - self.exec_command(cmd) + utils.exec_command(cmd) - def route_del_gateway(self, ifacename, gateway, metric=None): + def route_del_gateway(self, ifacename, gateway, vrf=None, metric=None): # delete default gw if not gateway: return - cmd = 'ip route del default via %s' %gateway + if not vrf: + cmd = 'ip route del default via %s' %gateway + else: + cmd = 'ip route del table %s default via %s' %(vrf, gateway) if metric: cmd += ' metric %s' %metric cmd += ' dev %s' %ifacename - self.exec_command(cmd) + utils.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) + return utils.exec_command('ip -6 route add default via %s dev %s' % + (gateway, 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) + return utils.exec_command('ip -6 route del default via %s dev %s' % + (gateway, 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) + utils.exec_command('ip link add link %s name %s type vlan id %d' % + (vlan_raw_device, vlan_device_name, 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 + return self.link_create_vlan(vlan_device_name, v[0], v[1]) def link_create_macvlan(self, name, linkdev, mode='private'): @@ -461,21 +527,23 @@ class iproute2(utilsBase): if self.ipbatch and not self.ipbatch_pause: self.add_to_batch(cmd) else: - self.exec_command('ip %s' %cmd) + utils.exec_command('ip %s' % cmd) self._cache_update([name], {}) - def get_vxlan_peers(self, dev): + def get_vxlan_peers(self, dev, svcnodeip): cmd = 'bridge fdb show brport %s' % dev cur_peers = [] try: - ps = subprocess.Popen((cmd).split(), stdout=subprocess.PIPE, close_fds=True) + ps = subprocess.Popen(shlex.split(cmd), stdout=subprocess.PIPE, close_fds=False) + utils.enable_subprocess_signal_forwarding(ps, signal.SIGINT) output = subprocess.check_output(('grep', '00:00:00:00:00:00'), stdin=ps.stdout) ps.wait() + utils.disable_subprocess_signal_forwarding(signal.SIGINT) try: ppat = re.compile('\s+dst\s+(\d+.\d+.\d+.\d+)\s+') for l in output.split('\n'): m = ppat.search(l) - if m: + if m and m.group(1) != svcnodeip: cur_peers.append(m.group(1)) except: self.logger.warn('error parsing ip link output') @@ -483,23 +551,23 @@ class iproute2(utilsBase): except subprocess.CalledProcessError as e: if e.returncode != 1: self.logger.error(str(e)) + finally: + utils.disable_subprocess_signal_forwarding(signal.SIGINT) return cur_peers def link_create_vxlan(self, name, vxlanid, localtunnelip=None, - svcnodeips=None, + svcnodeip=None, remoteips=None, learning='on', - ageing=None): - if svcnodeips and remoteips: + ageing=None, + anycastip=None): + if svcnodeip and remoteips: raise Exception("svcnodeip and remoteip is mutually exclusive") args = '' - if localtunnelip: - args += ' local %s' %localtunnelip - if svcnodeips: - for s in svcnodeips: - args += ' svcnode %s' %s + if svcnodeip: + args += ' remote %s' %svcnodeip if ageing: args += ' ageing %s' %ageing if learning == 'off': @@ -507,42 +575,57 @@ class iproute2(utilsBase): if self.link_exists(name): cmd = 'link set dev %s type vxlan dstport %d' %(name, VXLAN_UDP_PORT) + vxlanattrs = self.get_vxlandev_attrs(name) + # on ifreload do not overwrite anycast_ip to individual ip if clagd + # has modified + if vxlanattrs: + running_localtunnelip = vxlanattrs.get('local') + if anycastip and running_localtunnelip and anycastip == running_localtunnelip: + localtunnelip = running_localtunnelip + running_svcnode = vxlanattrs.get('svcnode') + if running_svcnode and not svcnodeip: + args += ' noremote' else: cmd = 'link add dev %s type vxlan id %s dstport %d' %(name, vxlanid, VXLAN_UDP_PORT) + + if localtunnelip: + args += ' local %s' %localtunnelip cmd += args if self.ipbatch and not self.ipbatch_pause: self.add_to_batch(cmd) else: - self.exec_command('ip %s' %cmd) + utils.exec_command('ip %s' % cmd) - # figure out the diff for remotes and do the bridge fdb updates - cur_peers = set(self.get_vxlan_peers(name)) - if remoteips: - new_peers = set(remoteips) - del_list = cur_peers.difference(new_peers) - add_list = new_peers.difference(cur_peers) - else: - del_list = cur_peers - add_list = [] + if not systemUtils.is_service_running(None, '/var/run/vxrd.pid'): + #figure out the diff for remotes and do the bridge fdb updates + #only if provisioned by user and not by vxrd + cur_peers = set(self.get_vxlan_peers(name, svcnodeip)) + if remoteips: + new_peers = set(remoteips) + del_list = cur_peers.difference(new_peers) + add_list = new_peers.difference(cur_peers) + else: + del_list = cur_peers + add_list = [] - try: - for addr in del_list: - self.bridge_fdb_del(name, '00:00:00:00:00:00', None, True, addr) - except: - pass + try: + for addr in del_list: + self.bridge_fdb_del(name, '00:00:00:00:00:00', None, True, addr) + except: + pass - try: - for addr in add_list: - self.bridge_fdb_append(name, '00:00:00:00:00:00', None, True, addr) - except: - pass + try: + for addr in add_list: + self.bridge_fdb_append(name, '00:00:00:00:00:00', None, True, addr) + except: + pass # XXX: update linkinfo correctly self._cache_update([name], {}) def link_exists(self, ifacename): - if self.DRYRUN: + if ifupdownflags.flags.DRYRUN: return True return os.path.exists('/sys/class/net/%s' %ifacename) @@ -552,20 +635,26 @@ class iproute2(utilsBase): return False def route_add(self, route): - self.exec_command('ip route add ' + route) + utils.exec_command('ip route add %s' % route) def route6_add(self, route): - self.exec_command('ip -6 route add ' + route) + utils.exec_command('ip -6 route add %s' % route) def get_vlandev_attrs(self, ifacename): - return (self._cache_get('link', [ifacename, 'linkinfo', 'link']), + return (self._cache_get('link', [ifacename, '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_linkinfo_attrs(self, ifacename): + return self._cache_get('link', [ifacename, 'linkinfo']) + + def link_get_mtu(self, ifacename, refresh=False): + return self._cache_get('link', [ifacename, 'mtu'], refresh=refresh) + + def link_get_kind(self, ifacename): + return self._cache_get('link', [ifacename, 'kind']) def link_get_hwaddress(self, ifacename): address = self._cache_get('link', [ifacename, 'hwaddress']) @@ -576,17 +665,21 @@ class iproute2(utilsBase): %ifacename) return address - def link_create(self, ifacename, type, link=None): + def link_create(self, ifacename, type, attrs={}): + """ generic link_create function """ if self.link_exists(ifacename): return cmd = 'link add' - if link: - cmd += ' link %s' %link cmd += ' name %s type %s' %(ifacename, type) + if attrs: + for k, v in attrs.iteritems(): + cmd += ' %s' %k + if v: + cmd += ' %s' %v if self.ipbatch and not self.ipbatch_pause: self.add_to_batch(cmd) else: - self.exec_command('ip %s' %cmd) + utils.exec_command('ip %s' % cmd) self._cache_update([ifacename], {}) def link_delete(self, ifacename): @@ -596,35 +689,46 @@ class iproute2(utilsBase): if self.ipbatch and not self.ipbatch_pause: self.add_to_batch(cmd) else: - self.exec_command('ip %s' %cmd) + utils.exec_command('ip %s' % cmd) self._cache_invalidate() + def link_get_master(self, ifacename): + sysfs_master_path = '/sys/class/net/%s/master' %ifacename + if os.path.exists(sysfs_master_path): + link_path = os.readlink(sysfs_master_path) + if link_path: + return os.path.basename(link_path) + else: + return None + else: + return self._cache_get('link', [ifacename, 'master']) + def bridge_port_vids_add(self, bridgeportname, vids): - [self.exec_command('bridge vlan add vid %s dev %s' - %(v, bridgeportname)) for v in vids] + [utils.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] + [utils.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_flush(self, bridgeportname, vid): + utils.exec_command('bridge vlan del vid %s dev %s' % + (vid, bridgeportname)) def bridge_port_vids_get(self, bridgeportname): - self.exec_command('/sbin/bridge vlan show %s' %bridgeportname) - bridgeout = self.exec_command('/sbin/bridge vlan show dev %s' - %bridgeportname) + utils.exec_command('/sbin/bridge vlan show %s' % bridgeportname) + bridgeout = utils.exec_command('/sbin/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] + return [v for v in vids if v] def bridge_port_vids_get_all(self): brvlaninfo = {} - bridgeout = self.exec_command('/sbin/bridge vlan show') + bridgeout = utils.exec_command('/sbin/bridge -c vlan show') if not bridgeout: return brvlaninfo brvlanlines = bridgeout.splitlines() brportname=None @@ -652,12 +756,12 @@ class iproute2(utilsBase): return brvlaninfo def bridge_port_pvid_add(self, bridgeportname, pvid): - self.exec_command('bridge vlan add vid %s untagged pvid dev %s' - %(pvid, bridgeportname)) + utils.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)) + utils.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' @@ -665,13 +769,13 @@ class iproute2(utilsBase): 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] + [utils.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] + [utils.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, remote=None): target = 'self' if bridge else '' @@ -683,21 +787,21 @@ class iproute2(utilsBase): if remote: dst_str = 'dst %s ' % remote - self.exec_command('bridge fdb replace %s dev %s %s %s %s' - %(address, dev, vlan_str, target, dst_str)) + utils.exec_command('bridge fdb replace %s dev %s %s %s %s' % + (address, dev, vlan_str, target, dst_str)) def bridge_fdb_append(self, dev, address, vlan=None, bridge=True, remote=None): target = 'self' if bridge else '' vlan_str = '' if vlan: vlan_str = 'vlan %s ' % vlan - + dst_str = '' if remote: dst_str = 'dst %s ' % remote - self.exec_command('bridge fdb append %s dev %s %s %s %s' - %(address, dev, vlan_str, target, dst_str)) + utils.exec_command('bridge fdb append %s dev %s %s %s %s' % + (address, dev, vlan_str, target, dst_str)) def bridge_fdb_del(self, dev, address, vlan=None, bridge=True, remote=None): target = 'self' if bridge else '' @@ -708,8 +812,8 @@ class iproute2(utilsBase): dst_str = '' if remote: dst_str = 'dst %s ' % remote - self.exec_command('bridge fdb del %s dev %s %s %s %s' - %(address, dev, vlan_str, target, dst_str)) + utils.exec_command('bridge fdb del %s dev %s %s %s %s' % + (address, dev, vlan_str, target, dst_str)) def bridge_is_vlan_aware(self, bridgename): filename = '/sys/class/net/%s/bridge/vlan_filtering' %bridgename @@ -734,7 +838,7 @@ class iproute2(utilsBase): def bridge_fdb_show_dev(self, dev): try: fdbs = {} - output = self.exec_command('bridge fdb show dev %s' %dev) + output = utils.exec_command('bridge fdb show dev %s' % dev) if output: for fdb_entry in output.splitlines(): try: @@ -758,14 +862,14 @@ class iproute2(utilsBase): iflags = int(flags, 16) if (iflags & 0x0001): ret = True - except: + except: ret = False pass return ret def ip_route_get_dev(self, prefix): try: - output = self.exec_command('ip route get %s' %prefix) + output = utils.exec_command('ip route get %s' % prefix) if output: rline = output.splitlines()[0] if rline: @@ -775,3 +879,25 @@ class iproute2(utilsBase): self.logger.debug('ip_route_get_dev: failed .. %s' %str(e)) pass return None + + def link_get_lowers(self, ifacename): + try: + lowers = glob.glob("/sys/class/net/%s/lower_*" %ifacename) + if not lowers: + return [] + return [os.path.basename(l)[6:] for l in lowers] + except: + return [] + + def link_get_upper(self, ifacename): + try: + upper = glob.glob("/sys/class/net/%s/upper_*" %ifacename) + if not upper: + return None + return os.path.basename(upper[0])[6:] + except: + return None + + def link_get_vrfs(self): + self._fill_cache() + return linkCache.vrfs diff --git a/ifupdownaddons/modulebase.py b/ifupdownaddons/modulebase.py index 47df790..6333270 100644 --- a/ifupdownaddons/modulebase.py +++ b/ifupdownaddons/modulebase.py @@ -8,14 +8,12 @@ import os import re import io import logging -import subprocess import traceback + +from ifupdown.utils import utils from ifupdown.iface import * -#from ifupdownaddons.iproute2 import * -#from ifupdownaddons.dhclient import * -#from ifupdownaddons.bridgeutils import * -#from ifupdownaddons.mstpctlutil import * -#from ifupdownaddons.ifenslaveutil import * +import ifupdown.policymanager as policymanager +import ifupdown.ifupdownflags as ifupdownflags class moduleBase(object): """ Base class for ifupdown addon modules @@ -25,157 +23,143 @@ class moduleBase(object): 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): + # vrfs are a global concept and a vrf context can be applicable + # to all global vrf commands. Get the default vrf-exec-cmd-prefix + # here so that all modules can use it + self.vrf_exec_cmd_prefix = policymanager.policymanager_api.get_module_globals('vrf', attr='vrf-exec-cmd-prefix') + + # explanations are shown in parse_glob + self.glob_regexs = [re.compile(r"([A-Za-z0-9\-]+)\[(\d+)\-(\d+)\]([A-Za-z0-9\-]+)\[(\d+)\-(\d+)\](.*)"), + re.compile(r"([A-Za-z0-9\-]+[A-Za-z])(\d+)\-(\d+)(.*)"), + re.compile(r"([A-Za-z0-9\-]+)\[(\d+)\-(\d+)\](.*)")] + + + def log_warn(self, str, ifaceobj=None): """ 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) + if ifaceobj: + ifaceobj.set_status(ifaceStatus.WARNING) pass - def log_error(self, str): + def log_error(self, str, ifaceobj=None, raise_error=True): """ 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) + if ifaceobj: + ifaceobj.set_status(ifaceStatus.ERROR) + if raise_error: + raise Exception(str) else: pass def is_process_running(self, procName): try: - self.exec_command('/bin/pidof -x %s' % procName) + utils.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: + for line in lines[2:]: ifacenames.append(line.split()[0].strip(': ')) except: raise return ifacenames - def parse_regex(self, expr, ifacenames=None): + def parse_regex(self, ifacename, expr, ifacenames=None): try: proc_ifacenames = self.get_ifaces_from_proc() except: - self.logger.warn('error reading ifaces from proc') + self.logger.warn('%s: error reading ifaces from proc' %ifacename) + for proc_ifacename in proc_ifacenames: - if re.search(expr + '$', proc_ifacename): - yield proc_ifacename + try: + if re.search(expr + '$', proc_ifacename): + yield proc_ifacename + except Exception, e: + raise Exception('%s: error searching regex \'%s\' in %s (%s)' + %(ifacename, expr, proc_ifacename, str(e))) if not ifacenames: return for ifacename in ifacenames: - if re.search(expr + '$', ifacename): - yield ifacename + try: + if re.search(expr + '$', ifacename): + yield ifacename + except Exception, e: + raise Exception('%s: error searching regex \'%s\' in %s (%s)' + %(ifacename, expr, ifacename, str(e))) - def parse_glob(self, expr): + def ifname_is_glob(self, ifname): + """ + Used by iface where ifname could be swp7 or swp[1-10].300 + """ + if (self.glob_regexs[0].match(ifname) or + self.glob_regexs[1].match(ifname) or + self.glob_regexs[2].match(ifname)): + return True + return False + + def parse_glob(self, ifacename, 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: + ' (supported glob syntax: swp1-10.300 or swp[1-10].300' + + ' or swp[1-10]sub[0-4].300') + regexs = self.glob_regexs + + if regexs[0].match(expr): + # the first regex checks for exactly two levels of ranges defined only with square brackets + # (e.g. swpxyz[10-23]subqwe[0-4].100) to handle naming with two levels of port names. + m = regexs[0].match(expr) + mlist = m.groups() + if len(mlist) < 7: + # we have problems and should not continue + raise Exception('%s: error: unhandled glob expression %s\n%s' % (ifacename, expr,errmsg)) + + prefix = mlist[0] + suffix = mlist[6] + start_index = int(mlist[1]) + end_index = int(mlist[2]) + sub_string = mlist[3] + start_sub = int(mlist[4]) + end_sub = int(mlist[5]) + for i in range(start_index, end_index + 1): + for j in range(start_sub, end_sub + 1): + yield prefix + '%d%s%d' % (i,sub_string,j) + suffix + + elif regexs[1].match(expr) or regexs[2].match(expr): + # the second regex for 1 level with a range (e.g. swp10-14.100 + # the third regex checks for 1 level with [] (e.g. swp[10-14].100) + start_index = 0 + end_index = 0 + if regexs[1].match(expr): + m = regexs[1].match(expr) + else: + m = regexs[2].match(expr) + mlist = m.groups() + if len(mlist) != 4: + raise Exception('%s: ' %ifacename + errmsg + '(unexpected len)') + prefix = mlist[0] + suffix = mlist[3] + start_index = int(mlist[1]) + end_index = int(mlist[2]) for i in range(start_index, end_index + 1): yield prefix + '%d' %i + suffix - def parse_port_list(self, port_expr, ifacenames=None): + else: + # Could not match anything. + self.logger.warn('%s: %s' %(ifacename, errmsg)) + yield expr + + def parse_port_list(self, ifacename, port_expr, ifacenames=None): """ parse port list containing glob and regex Args: @@ -188,7 +172,10 @@ class moduleBase(object): if not port_expr: return None - for expr in re.split(r'[\s\t]\s*', port_expr): + exprs = re.split(r'[\s\t]\s*', port_expr) + self.logger.debug('%s: evaluating port expr \'%s\'' + %(ifacename, str(exprs))) + for expr in exprs: if expr == 'noregex': regex = 0 elif expr == 'noglob': @@ -198,12 +185,12 @@ class moduleBase(object): elif expr == 'glob': glob = 1 elif regex: - for port in self.parse_regex(expr, ifacenames): + for port in self.parse_regex(ifacename, expr, ifacenames): if port not in portlist: portlist.append(port) regex = 0 elif glob: - for port in self.parse_glob(expr): + for port in self.parse_glob(ifacename, expr): portlist.append(port) glob = 0 else: @@ -213,7 +200,7 @@ class moduleBase(object): return portlist def ignore_error(self, errmsg): - if (self.FORCE or re.search(r'exists', errmsg, + if (ifupdownflags.flags.FORCE or re.search(r'exists', errmsg, re.IGNORECASE | re.MULTILINE)): return True return False @@ -223,7 +210,7 @@ class moduleBase(object): try: self.logger.info('writing \'%s\'' %strexpr + ' to file %s' %filename) - if self.DRYRUN: + if ifupdownflags.flags.DRYRUN: return 0 with open(filename, 'w') as f: f.write(strexpr) @@ -255,11 +242,11 @@ class moduleBase(object): def sysctl_set(self, variable, value): """ set sysctl variable to value passed as argument """ - self.exec_command('sysctl %s=' %variable + '%s' %value) + utils.exec_command('sysctl %s=%s' % (variable, value)) def sysctl_get(self, variable): """ get value of sysctl variable """ - return self.exec_command('sysctl %s' %variable).split('=')[1].strip() + return utils.exec_command('sysctl %s' % variable).split('=')[1].strip() def set_iface_attr(self, ifaceobj, attr_name, attr_valsetfunc, prehook=None, prehookargs=None): @@ -297,9 +284,17 @@ class moduleBase(object): 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""" + """ returns list of all module attrs defined in the module _modinfo + dict + """ try: - return self._modinfo.get('attrs').keys() + retattrs = [] + attrsdict = self._modinfo.get('attrs') + for attrname, attrvals in attrsdict.iteritems(): + if not attrvals or attrvals.get('deprecated'): + continue + retattrs.append(attrname) + return retattrs except: return None @@ -325,18 +320,13 @@ class moduleBase(object): 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('-') + (s, e) = utils.exec_command(get_resvvlan).strip('\n').split('-') start = int(s) end = int(e) except Exception, e: diff --git a/ifupdownaddons/mstpctlutil.py b/ifupdownaddons/mstpctlutil.py index 4716195..078ea9b 100644 --- a/ifupdownaddons/mstpctlutil.py +++ b/ifupdownaddons/mstpctlutil.py @@ -4,10 +4,13 @@ # Author: Roopa Prabhu, roopa@cumulusnetworks.com # +from cache import MSTPAttrsCache from utilsbase import * from ifupdown.iface import * +from ifupdown.utils import utils from cache import * import re +import json class mstpctlutil(utilsBase): """ This class contains helper methods to interact with mstpd using @@ -38,7 +41,7 @@ class mstpctlutil(utilsBase): def is_mstpd_running(self): try: - self.exec_command('/bin/pidof mstpd') + utils.exec_command('/bin/pidof mstpd') except: return False else: @@ -46,9 +49,9 @@ class mstpctlutil(utilsBase): 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') + cmdl = ['/sbin/mstpctl', 'showportdetail', bridgename, portname, + self._bridgeportattrmap[attrname]] + return utils.exec_commandl(cmdl).strip('\n') except Exception, e: pass return None @@ -65,13 +68,51 @@ class mstpctlutil(utilsBase): pass return bridgeattrs + def _get_mstpctl_bridgeport_attr_from_cache(self, bridgename): + attrs = MSTPAttrsCache.get(bridgename) + if not attrs: + try: + cmd = ['/sbin/mstpctl', 'showportdetail', bridgename, 'json'] + output = utils.exec_commandl(cmd) + if not output: + return None + except Exception as e: + self.logger.info(str(e)) + return None + mstpctl_bridgeport_attrs_dict = {} + try: + mstpctl_bridge_cache = json.loads(output.strip('\n')) + for portname in mstpctl_bridge_cache.keys(): + # we will ignore the portid for now and just index + # by bridgename, portname, and json attribute + for portid in mstpctl_bridge_cache[portname].keys(): + mstpctl_bridgeport_attrs_dict[portname] = {} + for jsonAttr in mstpctl_bridge_cache[portname][portid].keys(): + jsonVal = mstpctl_bridge_cache[portname][portid][jsonAttr] + mstpctl_bridgeport_attrs_dict[portname][jsonAttr] = str(jsonVal) + MSTPAttrsCache.set(bridgename, mstpctl_bridgeport_attrs_dict) + return mstpctl_bridgeport_attrs_dict + except Exception as e: + self.logger.info('%s: cannot fetch mstpctl bridge port attributes: %s', str(e)) + return attrs + + def get_mstpctl_bridgeport_attr(self, bridgename, portname, attr): + attrs = self._get_mstpctl_bridgeport_attr_from_cache(bridgename) + if not attrs: + return 'no' + else: + val = attrs.get(portname,{}).get(attr, 'no') + if val == 'True': + val = 'yes' + return str(val) + 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, + self.set_bridgeport_attr(bridgename, bridgeportname, k, v, check) except Exception, e: self.logger.warn(str(e)) @@ -84,11 +125,13 @@ class mstpctlutil(utilsBase): 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]) + utils.exec_commandl(['/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]) + utils.exec_commandl(['/sbin/mstpctl', 'set%s' % attrname, + '%s' % bridgename, '%s' % bridgeportname, + '%s' % attrvalue]) def get_bridge_attrs(self, bridgename): bridgeattrs = {} @@ -106,9 +149,9 @@ class mstpctlutil(utilsBase): def get_bridge_attr(self, bridgename, attrname): try: - return self.subprocess_check_output(['/sbin/mstpctl', - 'showbridge', '%s' %bridgename, - self._bridgeattrmap[attrname]]).strip('\n') + cmdl = ['/sbin/mstpctl', 'showbridge', bridgename, + self._bridgeattrmap[attrname]] + return utils.exec_commandl(cmdl).strip('\n') except Exception, e: pass return None @@ -120,11 +163,13 @@ class mstpctlutil(utilsBase): 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]) + utils.exec_commandl(['/sbin/mstpctl', 'set%s' % attrname, + '%s' % bridgename, '0', '%s' % attrvalue], + stdout=False, stderr=None) else: - self.subprocess_check_call(['/sbin/mstpctl', 'set%s' %attrname, - '%s' %bridgename, '%s' %attrvalue]) + utils.exec_commandl(['/sbin/mstpctl', 'set%s' % attrname, + '%s' % bridgename, '%s' % attrvalue], + stdout=False, stderr=None) def set_bridge_attrs(self, bridgename, attrdict, check=True): for k, v in attrdict.iteritems(): @@ -138,9 +183,12 @@ class mstpctlutil(utilsBase): def get_bridge_treeprio(self, bridgename): try: - bridgeid = subprocess.check_output(['/sbin/mstpctl', - 'showbridge', '%s' %bridgename, - self._bridgeattrmap['bridgeid']]).strip('\n') + cmdl = ['/sbin/mstpctl', + 'showbridge', + bridgename, + self._bridgeattrmap['bridgeid']] + + bridgeid = utils.exec_commandl(cmdl).strip('\n') return '%d' %(int(bridgeid.split('.')[0], base=16) * 4096) except: pass @@ -151,21 +199,21 @@ class mstpctlutil(utilsBase): 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]) + utils.exec_commandl(['/sbin/mstpctl', 'settreeprio', bridgename, '0', + str(attrvalue)]) def showbridge(self, bridgename=None): if bridgename: - return self.exec_command('/sbin/mstpctl showbridge %s' %bridgename) + return utils.exec_command('/sbin/mstpctl showbridge %s' % bridgename) else: - return self.exec_command('/sbin/mstpctl showbridge') + return utils.exec_command('/sbin/mstpctl showbridge') def showportdetail(self, bridgename): - return self.exec_command('/sbin/mstpctl showportdetail %s' %bridgename) + return utils.exec_command('/sbin/mstpctl showportdetail %s' % bridgename) def mstpbridge_exists(self, bridgename): try: - subprocess.check_call('mstpctl showbridge %s' %bridgename) + utils.exec_command('mstpctl showbridge %s' % bridgename, stdout=False) return True except: return False diff --git a/ifupdownaddons/systemutils.py b/ifupdownaddons/systemutils.py new file mode 100644 index 0000000..a6aad67 --- /dev/null +++ b/ifupdownaddons/systemutils.py @@ -0,0 +1,55 @@ +#!/usr/bin/python +# +# Copyright 2015 Cumulus Networks, Inc. All rights reserved. +# Author: Roopa Prabhu, roopa@cumulusnetworks.com +# + +import os +from utilsbase import * +from ifupdown.utils import utils + +class systemUtils(): + @classmethod + def is_service_running(cls, procname=None, pidfile=None): + utilsobj = utilsBase() + if pidfile: + if os.path.exists(pidfile): + pid = utilsobj.read_file_oneline(pidfile) + if not os.path.exists('/proc/%s' %pid): + return False + else: + return False + return True + + if procname: + try: + utils.exec_command('/bin/pidof %s' % procname, stdout=False) + except: + return False + else: + return True + + return False + + @classmethod + def check_service_status(cls, servicename=None): + if not servicename: + return False + try: + utils.exec_commandl(['/usr/sbin/service', servicename, 'status'], + stdout=False) + except Exception: + # XXX: check for subprocess errors vs os error + return False + return True + + @classmethod + def is_process_running(self, processname): + if not processname: + return False + try: + utils.exec_command('/bin/pidof %s' % processname, stdout=False) + except: + return False + else: + return True diff --git a/ifupdownaddons/utilsbase.py b/ifupdownaddons/utilsbase.py index 73fdfd5..477fcc8 100644 --- a/ifupdownaddons/utilsbase.py +++ b/ifupdownaddons/utilsbase.py @@ -5,13 +5,13 @@ # import logging -import subprocess import re import io + +from ifupdown.utils import utils +import ifupdown.ifupdownflags as ifupdownflags from ifupdown.iface import * from cache import * - -#import timeit import time import logging @@ -30,105 +30,12 @@ class utilsBase(object): 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: + if ifupdownflags.flags.DRYRUN: return 0 with open(filename, 'w') as f: f.write(strexpr) @@ -157,7 +64,7 @@ class utilsBase(object): return None def sysctl_set(self, variable, value): - self.exec_command('sysctl %s=' %variable + '%s' %value) + utils.exec_command('sysctl %s=%s' % (variable, value)) def sysctl_get(self, variable): - return self.exec_command('sysctl %s' %variable).split('=')[1].strip() + return utils.exec_command('sysctl %s' % variable).split('=')[1].strip() diff --git a/init.d/networking b/init.d/networking index 45d3d78..09ead81 100644 --- a/init.d/networking +++ b/init.d/networking @@ -11,7 +11,10 @@ PATH="/sbin:/bin" RUN_DIR="/run/network" -IFSTATE="$RUN_DIR/ifstate" +IFSTATE_LOCKFILE="${RUN_DIR}/ifstatelock" + +STATE_DIR="/var/tmp/network" +IFSTATE_FILE="${STATE_DIR}/ifstatenew" NAME=networking SCRIPTNAME=/etc/init.d/$NAME @@ -37,7 +40,7 @@ gen_examples() { # # generate files only at boot - [ -f /var/tmp/network/ifstatenew ] && return + [ -f ${IFSTATE_LOCKFILE} ] && return python_ifupdown2_docdir="/usr/share/doc/python-ifupdown2" swpfile=${python_ifupdown2_docdir}"/examples/swp_defaults" @@ -52,7 +55,7 @@ gen_examples() { perf_options() { # At bootup lets set perfmode - [ -f /var/tmp/network/ifstatenew ] && echo -n "" && return + [ -f ${IFSTATE_LOCKFILE} ] && echo -n "" && return echo -n "--perfmode" } @@ -124,6 +127,8 @@ ifup_hotplug () { } ifupdown_init() { + # remove state file at boot + [ ! -e ${IFSTATE_LOCKFILE} ] && rm -f ${IFSTATE_FILE} [ ! -e /run/network ] && mkdir -p /run/network &>/dev/null [ ! -e /etc/network/run ] && \ ln -sf /run/network /etc/network/run &>/dev/null diff --git a/man.rst/ifquery.8.rst b/man.rst/ifquery.8.rst index ba443ce..ff3a3ce 100644 --- a/man.rst/ifquery.8.rst +++ b/man.rst/ifquery.8.rst @@ -33,7 +33,12 @@ DESCRIPTION **ifquery** always works on the current **interfaces(5)** file **/etc/network/interfaces** unless an alternate interfaces file is +<<<<<<< HEAD provided with the **-i** option. +======= + defined in ifupdown2.conf or provided with the **-i** option. + Note: the -i option is disabled by default in ifupdown2.conf. +>>>>>>> cumulus/dev OPTIONS ======= @@ -60,10 +65,21 @@ OPTIONS -X EXCLUDEPATS, --exclude EXCLUDEPATS Exclude interfaces from the list of interfaces to operate on. Can be specified multiple times +<<<<<<< HEAD -i INTERFACESFILE, --interfaces INTERFACESFILE Use interfaces file instead of default /etc/network/interfaces +======= + If the excluded interface has dependent interfaces, + (e.g. a bridge or a bond with multiple enslaved interfaces) + then each dependent interface must be specified in order + to be excluded. + + -i INTERFACESFILE, --interfaces INTERFACESFILE + Use interfaces file instead of default + defined in ifupdown2.conf (default /etc/network/interfaces) +>>>>>>> cumulus/dev -t {native,json}, --interfaces-format {native,json} interfaces file format diff --git a/man.rst/ifreload.8.rst b/man.rst/ifreload.8.rst index f26442b..b946abf 100644 --- a/man.rst/ifreload.8.rst +++ b/man.rst/ifreload.8.rst @@ -14,11 +14,20 @@ reload network interface configuration SYNOPSIS ======== +<<<<<<< HEAD ifreload [-h] (-a|-c) [-v] [-d] [-f] [-n] DESCRIPTION =========== reloads network **interfaces(5)** file **/etc/network/interfaces**. +======= + ifreload [-h] (-a|-c) [-v] [-d] [-f] [-n] [-s] + +DESCRIPTION +=========== + reloads network **interfaces(5)** file **/etc/network/interfaces** + or config file defined in ifupdown2.conf file. +>>>>>>> cumulus/dev Runs **ifdown** on interfaces that were removed from the file and subsequently runs **ifup** on all interfaces. @@ -55,6 +64,15 @@ OPTIONS -X EXCLUDEPATS, --exclude EXCLUDEPATS Exclude interfaces from the list of interfaces to operate on. Can be specified multiple times +<<<<<<< HEAD +======= + If the excluded interface has dependent interfaces, + (e.g. a bridge or a bond with multiple enslaved interfaces) + then each dependent interface must be specified in order + to be excluded. + + -s, --syntax-check Only run the interfaces file parser +>>>>>>> cumulus/dev EXAMPLES diff --git a/man.rst/ifup.8.rst b/man.rst/ifup.8.rst index 407b44b..1a5ffe8 100644 --- a/man.rst/ifup.8.rst +++ b/man.rst/ifup.8.rst @@ -22,7 +22,11 @@ SYNOPSIS ======== ifup [-h] [-a] [-v] [-d] [--allow CLASS] [--with-depends] +<<<<<<< HEAD **[-X EXCLUDEPATS] [-f] [-n] [--print-dependency {list,dot}]** +======= + **[-X EXCLUDEPATS] [-f] [-n] [-s] [--print-dependency {list,dot}]** +>>>>>>> cumulus/dev **[IFACE [IFACE ...]]** ifdown [-h] [-a] [-v] [-d] [--allow CLASS] [--with-depends] @@ -33,13 +37,22 @@ DESCRIPTION =========== **ifup** and **ifdown** commands can be used to configure (or, respectively, deconfigure) network interfaces based on interface definitions in the +<<<<<<< HEAD file **/etc/network/interfaces/** file. +======= + config file ifupdown2.conf (defaults to **/etc/network/interfaces/** file). +>>>>>>> cumulus/dev **ifquery(8)** maybe used in conjunction with **ifup** and **ifdown** commands to query and validate applied/running configuration. +<<<<<<< HEAD **ifup** always works on the current **interfaces(5)** file under **/etc/network/interfaces**. **ifdown** works on the last applied interface +======= + **ifup** always works on the current **interfaces(5)** file defined in ifupdown2.conf + (default **/etc/network/interfaces**). **ifdown** works on the last applied interface +>>>>>>> cumulus/dev configuration. **ifup** on an already ifup'ed interface will re-apply the configuration, @@ -82,10 +95,24 @@ OPTIONS -X EXCLUDEPATS, --exclude EXCLUDEPATS Exclude interfaces from the list of interfaces to operate on. Can be specified multiple times +<<<<<<< HEAD -i INTERFACESFILE, --interfaces INTERFACESFILE Use interfaces file instead of default /etc/network/interfaces +======= + If the excluded interface has dependent interfaces, + (e.g. a bridge or a bond with multiple enslaved interfaces) + then each dependent interface must be specified in order + to be excluded. + + -i INTERFACESFILE, --interfaces INTERFACESFILE + Uses interfaces file instead of default defined in + ifupdown2.conf (default /etc/network/interfaces). + Also in ifupdown2.conf, users are not allowed to specify their own + interfaces file unless disable_cli_interfacesfile is set to 0 + (default is 1). +>>>>>>> cumulus/dev -t {native,json}, --interfaces-format {native,json} interfaces file format @@ -108,6 +135,11 @@ OPTIONS your state file is corrupted or you want down to use the latest from the interfaces file +<<<<<<< HEAD +======= + -s, --syntax-check Only run the interfaces file parser + +>>>>>>> cumulus/dev EXAMPLES ======== # bringing up all interfaces diff --git a/man.rst/ifupdown-addons-interfaces.5.rst b/man.rst/ifupdown-addons-interfaces.5.rst index 94e2f7c..75ec167 100644 --- a/man.rst/ifupdown-addons-interfaces.5.rst +++ b/man.rst/ifupdown-addons-interfaces.5.rst @@ -58,12 +58,21 @@ EXAMPLES **required**: False +<<<<<<< HEAD **default**: off **validvals**: on,off **example**: link-autoneg on +======= + **default**: no + + **validvals**: yes,no + + **example**: + link-autoneg yes +>>>>>>> cumulus/dev **link-speed** @@ -182,10 +191,19 @@ EXAMPLES **required**: False +<<<<<<< HEAD **default**: 0 **example**: bridge-mcquerier 0 +======= + **default**: no + + **validvals**: yes,no + + **example**: + bridge-mcquerier no +>>>>>>> cumulus/dev **bridge-mclmc** @@ -221,10 +239,19 @@ EXAMPLES **required**: False +<<<<<<< HEAD **default**: 1 **example**: bridge-mcrouter 1 +======= + **default**: yes + + **validvals**: yes,no + + **example**: + bridge-mcrouter yes +>>>>>>> cumulus/dev **bridge-stp** @@ -321,6 +348,11 @@ EXAMPLES bridge-ports glob swp1-3.100 +<<<<<<< HEAD +======= + bridge-ports glob swp[1-3]s[0-4].100 + +>>>>>>> cumulus/dev bridge-ports regex (swp[1|2|3].100) @@ -331,10 +363,19 @@ EXAMPLES **required**: False +<<<<<<< HEAD **default**: 0 **example**: bridge-mcqifaddr 0 +======= + **default**: no + + **validvals**: yes,no + + **example**: + bridge-mcqifaddr no +>>>>>>> cumulus/dev **bridge-waitport** @@ -439,10 +480,19 @@ EXAMPLES **required**: False +<<<<<<< HEAD **default**: 1 **example**: bridge-mcsnoop 1 +======= + **default**: yes + + **validvals**: yes,no + + **example**: + bridge-mcsnoop yes +>>>>>>> cumulus/dev **bridge-access** @@ -966,7 +1016,11 @@ EXAMPLES +<<<<<<< HEAD **ifenslave**: bond configuration module +======= + **bond**: bond configuration module +>>>>>>> cumulus/dev **bond-use-carrier** @@ -976,12 +1030,21 @@ EXAMPLES **required**: False +<<<<<<< HEAD **default**: 1 **validvals**: 0,1 **example**: bond-use-carrier 1 +======= + **default**: yes + + **validvals**: yes,no + + **example**: + bond-use-carrier yes +>>>>>>> cumulus/dev **bond-lacp-bypass-period** @@ -1075,12 +1138,21 @@ EXAMPLES **required**: False +<<<<<<< HEAD **default**: 0 **validvals**: 0,1 **example**: bond-lacp-bypass-allow 0 +======= + **default**: no + + **validvals**: yes,no + + **example**: + bond-lacp-bypass-allow no +>>>>>>> cumulus/dev **bond-mode** @@ -1306,15 +1378,28 @@ EXAMPLES **vxlan-learning** +<<<<<<< HEAD **help**: vxlan learning on/off +======= + **help**: vxlan learning yes/no +>>>>>>> cumulus/dev **required**: False +<<<<<<< HEAD **default**: on **example**: vxlan-learning off +======= + **default**: yes + + **validvals**: yes,no + + **example**: + vxlan-learning no +>>>>>>> cumulus/dev **vxlan-id** diff --git a/man.rst/interfaces.5.rst b/man.rst/interfaces.5.rst index e77e1c9..f673f3c 100644 --- a/man.rst/interfaces.5.rst +++ b/man.rst/interfaces.5.rst @@ -14,8 +14,14 @@ network interface configuration for ifupdown DESCRIPTION =========== +<<<<<<< HEAD **/etc/network/interfaces** contains network interface configuration information for the **ifup(8)**, **ifdown(8)** and **ifquery(8)** commands. +======= + By default, ifupdown2.conf sets **/etc/network/interfaces** as the + network interface configuration file. This file contains information + for the **ifup(8)**, **ifdown(8)** and **ifquery(8)** commands. +>>>>>>> cumulus/dev This is where you configure how your system is connected to the network. @@ -145,7 +151,11 @@ EXAMPLES FILES ===== +<<<<<<< HEAD /etc/network/interfaces +======= + configuration file defined in ifupdown2.conf (default /etc/network/interfaces) +>>>>>>> cumulus/dev SEE ALSO ======== diff --git a/nlmanager/README b/nlmanager/README new file mode 100644 index 0000000..8adac90 --- /dev/null +++ b/nlmanager/README @@ -0,0 +1,6 @@ +DO NOT EDIT NLMANAGER SOURCES. +This is a mirror copy of python-nlmanager sources. +It was extracted and directly included here to support some usecases where +user don't have python-nlmanager already installed on their system. So we +decided to have local copy and build with it. It is the mainter responsability +to keep an updated version of nlmanager. \ No newline at end of file diff --git a/nlmanager/__init__.py b/nlmanager/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/nlmanager/__init__.py @@ -0,0 +1 @@ + diff --git a/nlmanager/nllistener.py b/nlmanager/nllistener.py new file mode 100644 index 0000000..a3b56e9 --- /dev/null +++ b/nlmanager/nllistener.py @@ -0,0 +1,538 @@ +#!/usr/bin/env python + +from nlpacket import * +from nlmanager import NetlinkManager +from select import select +from struct import pack, unpack, calcsize +from threading import Thread, Event, Lock +from Queue import Queue +import logging +import socket + +log = logging.getLogger(__name__) + + +class NetlinkListener(Thread): + + def __init__(self, manager, groups): + """ + groups controls what types of messages we are interested in hearing + To get everything pass: + RTMGRP_LINK | \ + RTMGRP_IPV4_IFADDR | \ + RTMGRP_IPV4_ROUTE | \ + RTMGRP_IPV6_IFADDR | \ + RTMGRP_IPV6_ROUTE + """ + Thread.__init__(self) + self.manager = manager + self.shutdown_event = Event() + self.groups = groups + + def __str__(self): + return 'NetlinkListener' + + def run(self): + manager = self.manager + header_PACK = 'IHHII' + header_LEN = calcsize(header_PACK) + + # The RX socket is used to listen to all netlink messages that fly by + # as things change in the kernel. We need a very large SO_RCVBUF here + # else we tend to miss messages. + self.rx_socket = socket.socket(socket.AF_NETLINK, socket.SOCK_RAW, 0) + self.rx_socket.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 10000000) + self.rx_socket.bind((manager.pid+1, self.groups)) + self.rx_socket_prev_seq = {} + + if not manager.tx_socket: + manager.tx_socket_allocate() + + my_sockets = (manager.tx_socket, self.rx_socket) + + socket_string = { + manager.tx_socket: "TX", + self.rx_socket: "RX" + } + + supported_messages = (RTM_NEWLINK, RTM_DELLINK, RTM_NEWADDR, + RTM_DELADDR, RTM_NEWNEIGH, RTM_DELNEIGH, + RTM_NEWROUTE, RTM_DELROUTE) + + ignore_messages = (RTM_GETLINK, RTM_GETADDR, RTM_GETNEIGH, + RTM_GETROUTE, RTM_GETQDISC, NLMSG_ERROR, NLMSG_DONE) + + while True: + + if self.shutdown_event.is_set(): + log.info("%s: shutting down" % self) + return + + # Only block for 1 second so we can wake up to see + try: + (readable, writeable, exceptional) = select(my_sockets, [], my_sockets, 1) + except Exception as e: + log.error('select() error: ' + str(e)) + continue + + if not readable: + continue + + set_alarm = False + set_tx_socket_rxed_ack_alarm = False + missed_a_packet = False + + for s in readable: + data = None + + try: + data = s.recv(4096) + + # If recv() failed, we missed a packet + except Exception as e: + log.error('recv() error: ' + str(e)) + missed_a_packet = True + continue + + total_length = len(data) + while data: + + # Extract the length, etc from the header + (length, msgtype, flags, seq, pid) = unpack(header_PACK, data[:header_LEN]) + + if manager.debug_listener: + log.info('%s: RXed %s seq %d, pid %d, %d bytes (%d total)' % + (socket_string[s], NetlinkPacket.type_to_string[msgtype], + seq, pid, length, total_length)) + + # 99% of the time when we see an ERROR the error code is + # zero which means ACK + possible_ack = False + + if msgtype == NLMSG_DONE: + possible_ack = True + + elif msgtype == NLMSG_ERROR: + # TODO - change this > to = ? + error_code = int(unpack('>H', data[header_LEN:header_LEN+2])[0]) + + if error_code: + log.debug("%s: RXed NLMSG_ERROR code %d" % (socket_string[s], error_code)) + else: + possible_ack = True + + if possible_ack and seq == manager.target_seq and pid == manager.target_pid: + if manager.target_seq_pid_debug: + log.debug("%s: Setting RXed ACK alarm for seq %d, pid %d" % + (socket_string[s], seq, pid)) + set_tx_socket_rxed_ack_alarm = True + + # Put the message on the manager's netlinkq + if msgtype in supported_messages: + set_alarm = True + manager.netlinkq.append((msgtype, length, flags, seq, pid, data[0:length])) + + # There are certain message types we do not care about + # (RTM_GETs for example) + elif msgtype in ignore_messages: + pass + + # And there are certain message types we have not added + # support for yet (QDISC). Log an error for these just + # as a reminder to add support for them. + else: + if msgtype in NetlinkPacket.type_to_string: + log.warning('%s: RXed unsupported message %s (type %d)' % + (socket_string[s], NetlinkPacket.type_to_string[msgtype], msgtype)) + else: + log.warning('%s: RXed unknown message type %d' % + (socket_string[s], msgtype)) + + # Track the previous PID sequence number for RX and TX sockets + if s == self.rx_socket: + prev_seq = self.rx_socket_prev_seq + elif s == manager.tx_socket: + prev_seq = manager.tx_socket_prev_seq + + if pid in prev_seq and prev_seq[pid] and prev_seq[pid] != seq and (prev_seq[pid]+1 != seq): + log.info('%s: Went from seq %d to %d' % (socket_string[s], prev_seq[pid], seq)) + missed_a_packet = True + prev_seq[pid] = seq + + data = data[length:] + + if set_tx_socket_rxed_ack_alarm: + manager.target_lock.acquire() + manager.target_seq = None + manager.target_pid = None + manager.target_lock.release() + manager.tx_socket_rxed_ack.set() + + if set_alarm: + manager.workq.put(('SERVICE_NETLINK_QUEUE', None)) + manager.alarm.set() + + self.rx_socket.close() + + +class NetlinkManagerWithListener(NetlinkManager): + + def __init__(self, groups, start_listener=True): + NetlinkManager.__init__(self) + self.groups = groups + self.workq = Queue() + self.netlinkq = [] + self.alarm = Event() + self.shutdown_event = Event() + self.tx_socket_rxed_ack = Event() + self.tx_socket_rxed_ack.clear() + self.target_seq = None + self.target_pid = None + self.target_seq_pid_debug = False + self.target_lock = Lock() + self.tx_socket_prev_seq = {} + self.debug_listener = False + self.debug_seq_pid = {} + self.ifname_by_index = {} + self.blacklist_filter = {} + self.whitelist_filter = {} + + # Listen to netlink messages + if start_listener: + self.listener = NetlinkListener(self, self.groups) + self.listener.start() + else: + self.listener = None + + def __str__(self): + return 'NetlinkManagerWithListener' + + def signal_term_handler(self, signal, frame): + log.info("NetlinkManagerWithListener: Caught SIGTERM") + self.shutdown_flag = True # For NetlinkManager shutdown + self.shutdown_event.set() + self.alarm.set() + + def signal_int_handler(self, signal, frame): + log.info("NetlinkManagerWithListener: Caught SIGINT") + self.shutdown_flag = True # For NetlinkManager shutdown + self.shutdown_event.set() + self.alarm.set() + + def tx_nlpacket_ack_on_listener(self, nlpacket): + """ + TX the message and wait for an ack + """ + + # NetlinkListener looks at the manager's target_seq and target_pid + # to know when we've RXed the ack that we want + self.target_lock.acquire() + self.target_seq = nlpacket.seq + self.target_pid = nlpacket.pid + self.target_lock.release() + + if not self.tx_socket: + self.tx_socket_allocate() + self.tx_socket.sendall(nlpacket.message) + + # Wait for NetlinkListener to RX an ACK or DONE for this (seq, pid) + self.tx_socket_rxed_ack.wait() + self.tx_socket_rxed_ack.clear() + + # These are here to show some basic examples of how one might react to RXing + # various netlink message types. Odds are our child class will redefine these + # to do more than log a message. + def rx_rtm_newlink(self, msg): + log.info("RX RTM_NEWLINK for %s, state %s" % (msg.get_attribute_value(msg.IFLA_IFNAME), "up" if msg.is_up() else "down")) + + def rx_rtm_dellink(self, msg): + log.info("RX RTM_DELLINK for %s, state %s" % (msg.get_attribute_value(msg.IFLA_IFNAME), "up" if msg.is_up() else "down")) + + def rx_rtm_newaddr(self, msg): + log.info("RX RTM_NEWADDR for %s/%d on %s" % + (msg.get_attribute_value(msg.IFA_ADDRESS), msg.prefixlen, self.ifname_by_index.get(msg.ifindex))) + + def rx_rtm_deladdr(self, msg): + log.info("RX RTM_DELADDR for %s/%d on %s" % + (msg.get_attribute_value(msg.IFA_ADDRESS), msg.prefixlen, self.ifname_by_index.get(msg.ifindex))) + + def rx_rtm_newneigh(self, msg): + log.info("RX RTM_NEWNEIGH for %s on %s" % (msg.get_attribute_value(msg.NDA_DST), self.ifname_by_index.get(msg.ifindex))) + + def rx_rtm_delneigh(self, msg): + log.info("RX RTM_DELNEIGH for %s on %s" % (msg.get_attribute_value(msg.NDA_DST), self.ifname_by_index.get(msg.ifindex))) + + def rx_rtm_newroute(self, msg): + log.info("RX RTM_NEWROUTE for %s%s" % + (msg.get_prefix_string(), msg.get_nexthops_string(self.ifname_by_index))) + + def rx_rtm_delroute(self, msg): + log.info("RX RTM_NEWROUTE for %s%s" % + (msg.get_prefix_string(), msg.get_nexthops_string(self.ifname_by_index))) + + # Note that tx_nlpacket_ack_on_listener will block until NetlinkListener has RXed + # an Ack/DONE for the message we TXed + def get_all_addresses(self): + family = socket.AF_UNSPEC + debug = RTM_GETADDR in self.debug + + addr = Address(RTM_GETADDR, debug) + addr.flags = NLM_F_REQUEST | NLM_F_DUMP + addr.body = pack('Bxxxi', family, 0) + addr.build_message(self.sequence.next(), self.pid) + + if debug: + self.debug_seq_pid[(addr.seq, addr.pid)] = True + + self.tx_nlpacket_ack_on_listener(addr) + + def get_all_links(self): + family = socket.AF_UNSPEC + debug = RTM_GETLINK in self.debug + + link = Link(RTM_GETLINK, debug) + link.flags = NLM_F_REQUEST | NLM_F_DUMP + link.body = pack('Bxxxiii', family, 0, 0, 0) + link.build_message(self.sequence.next(), self.pid) + + if debug: + self.debug_seq_pid[(link.seq, link.pid)] = True + + self.tx_nlpacket_ack_on_listener(link) + + def get_all_neighbors(self): + family = socket.AF_UNSPEC + debug = RTM_GETNEIGH in self.debug + + neighbor = Neighbor(RTM_GETNEIGH, debug) + neighbor.flags = NLM_F_REQUEST | NLM_F_DUMP + neighbor.body = pack('Bxxxii', family, 0, 0) + neighbor.build_message(self.sequence.next(), self.pid) + + if debug: + self.debug_seq_pid[(neighbor.seq, neighbor.pid)] = True + + self.tx_nlpacket_ack_on_listener(neighbor) + + def get_all_routes(self): + family = socket.AF_UNSPEC + debug = RTM_GETROUTE in self.debug + + route = Route(RTM_GETROUTE, debug) + route.flags = NLM_F_REQUEST | NLM_F_DUMP + route.body = pack('Bxxxii', family, 0, 0) + route.build_message(self.sequence.next(), self.pid) + + if debug: + self.debug_seq_pid[(route.seq, route.pid)] = True + + self.tx_nlpacket_ack_on_listener(route) + + def nested_attributes_match(self, msg, attr_filter): + """ + attr_filter will be a dictionary such as: + attr_filter = { + Link.IFLA_LINKINFO: { + Link.IFLA_INFO_KIND: 'vlan' + } + } + """ + for (key, value) in attr_filter.items(): + if type(value) is dict: + if not self.nested_attributes_match(msg, value): + return False + else: + attr_value = msg.get_attribute_value(key) + if attr_value != value: + return False + return True + + def filter_rule_matches(self, msg, rule): + field = rule[0] + options = rule[1:] + + if field == 'IFINDEX': + ifindex = options[0] + + if msg.ifindex == ifindex: + return True + + elif field == 'ATTRIBUTE': + (attr_type, target_value) = options[0:2] + attr_value = msg.get_attribute_value(attr_type) + + if attr_value == target_value: + return True + + elif field == 'NESTED_ATTRIBUTE': + if self.nested_attributes_match(msg, options[0]): + return True + + elif field == 'FAMILY': + family = options[0] + + if msg.family == family: + return True + else: + raise Exception("Add support to filter based on %s" % field) + + return False + + def filter_permit(self, msg): + """ + Return True if our whitelist/blacklist filters permit this netlink msg + """ + if msg.msgtype in self.whitelist_filter: + found_it = False + + for rule in self.whitelist_filter[msg.msgtype]: + if self.filter_rule_matches(msg, rule): + found_it = True + break + + return found_it + + elif msg.msgtype in self.blacklist_filter: + for rule in self.blacklist_filter[msg.msgtype]: + if self.filter_rule_matches(msg, rule): + return False + return True + + else: + return True + + def _filter_update(self, add, filter_type, msgtype, filter_guts): + assert filter_type in ('whitelist', 'blacklist'), "whitelist and blacklist are the only supported filter options" + + if add: + if filter_type == 'whitelist': + + # Keep things simple, do not allow both whitelist and blacklist + if self.blacklist_filter and self.blacklist_filter.get(msgtype): + raise Exception("whitelist and blacklist filters cannot be used at the same time") + + if msgtype not in self.whitelist_filter: + self.whitelist_filter[msgtype] = [] + self.whitelist_filter[msgtype].append(filter_guts) + + elif filter_type == 'blacklist': + + # Keep things simple, do not allow both whitelist and blacklist + if self.whitelist_filter and self.whitelist_filter.get(msgtype): + raise Exception("whitelist and blacklist filters cannot be used at the same time") + + if msgtype not in self.blacklist_filter: + self.blacklist_filter[msgtype] = [] + self.blacklist_filter[msgtype].append(filter_guts) + + else: + if filter_type == 'whitelist': + if msgtype in self.whitelist_filter: + self.whitelist_filter[msgtype].remove(filter_guts) + + if not self.whitelist_filter[msgtype]: + del self.whitelist_filter[msgtype] + + elif filter_type == 'blacklist': + if msgtype in self.blacklist_filter: + self.blacklist_filter[msgtype].remove(filter_guts) + + if not self.blacklist_filter[msgtype]: + del self.blacklist_filter[msgtype] + + def filter_by_address_family(self, add, filter_type, msgtype, family): + self._filter_update(add, filter_type, msgtype, ('FAMILY', family)) + + def filter_by_ifindex(self, add, filter_type, msgtype, ifindex): + self._filter_update(add, filter_type, msgtype, ('IFINDEX', ifindex)) + + def filter_by_attribute(self, add, filter_type, msgtype, attribute, attribute_value): + self._filter_update(add, filter_type, msgtype, ('ATTRIBUTE', attribute, attribute_value)) + + def filter_by_nested_attribute(self, add, filter_type, msgtype, attr_filter): + self._filter_update(add, filter_type, msgtype, ('NESTED_ATTRIBUTE', attr_filter)) + + def service_netlinkq(self): + msg_count = {} + processed = 0 + + for (msgtype, length, flags, seq, pid, data) in self.netlinkq: + processed += 1 + + # If this is a reply to a TX message that debugs were enabled for then debug the reply + if (seq, pid) in self.debug_seq_pid: + debug = True + else: + debug = self.debug_this_packet(msgtype) + + if msgtype == RTM_NEWLINK or msgtype == RTM_DELLINK: + msg = Link(msgtype, debug) + + elif msgtype == RTM_NEWADDR or msgtype == RTM_DELADDR: + msg = Address(msgtype, debug) + + elif msgtype == RTM_NEWNEIGH or msgtype == RTM_DELNEIGH: + msg = Neighbor(msgtype, debug) + + elif msgtype == RTM_NEWROUTE or msgtype == RTM_DELROUTE: + msg = Route(msgtype, debug) + + else: + log.warning('RXed unknown netlink message type %s' % msgtype) + continue + + msg.decode_packet(length, flags, seq, pid, data) + + if not self.filter_permit(msg): + continue + + if debug: + msg.dump() + + # Only used for printing debugs about how many we RXed of each type + if msg.msgtype not in msg_count: + msg_count[msg.msgtype] = 0 + msg_count[msg.msgtype] += 1 + + # Call the appropriate handler method based on the msgtype. The handler + # functions are defined in our child class. + if msg.msgtype == RTM_NEWLINK: + + # We will use ifname_by_index to display the interface name in debug output + self.ifname_by_index[msg.ifindex] = msg.get_attribute_value(msg.IFLA_IFNAME) + self.rx_rtm_newlink(msg) + + elif msg.msgtype == RTM_DELLINK: + + # We will use ifname_by_index to display the interface name in debug output + if msg.ifindex in self.ifname_by_index: + del self.ifname_by_index[msg.ifindex] + self.rx_rtm_dellink(msg) + + elif msg.msgtype == RTM_NEWADDR: + self.rx_rtm_newaddr(msg) + + elif msg.msgtype == RTM_DELADDR: + self.rx_rtm_deladdr(msg) + + elif msg.msgtype == RTM_NEWNEIGH: + self.rx_rtm_newneigh(msg) + + elif msg.msgtype == RTM_DELNEIGH: + self.rx_rtm_delneigh(msg) + + elif msg.msgtype == RTM_NEWROUTE: + self.rx_rtm_newroute(msg) + + elif msg.msgtype == RTM_DELROUTE: + self.rx_rtm_delroute(msg) + + else: + log.warning('RXed unknown netlink message type %s' % msgtype) + + if processed: + self.netlinkq = self.netlinkq[processed:] + + # too chatty + # for msgtype in msg_count: + # log.debug('RXed %d %s messages' % (msg_count[msgtype], NetlinkPacket.type_to_string[msgtype])) diff --git a/nlmanager/nlmanager.py b/nlmanager/nlmanager.py new file mode 100644 index 0000000..491a516 --- /dev/null +++ b/nlmanager/nlmanager.py @@ -0,0 +1,591 @@ +#!/usr/bin/env python + +from ipaddr import IPv4Address, IPv6Address +from nlpacket import * +from select import select +from struct import pack, unpack +from tabulate import tabulate +import logging +import os +import socket + +log = logging.getLogger(__name__) + + +class NetlinkError(Exception): + pass + + +class NetlinkNoAddressError(Exception): + pass + + +class InvalidInterfaceNameVlanCombo(Exception): + pass + + +class Sequence(object): + + def __init__(self): + self._next = 0 + + def next(self): + self._next += 1 + return self._next + + +class NetlinkManager(object): + + def __init__(self): + self.pid = os.getpid() + self.sequence = Sequence() + self.shutdown_flag = False + self.ifindexmap = {} + self.tx_socket = None + + # debugs + self.debug = {} + self.debug_link(False) + self.debug_address(False) + self.debug_neighbor(False) + self.debug_route(False) + + def __str__(self): + return 'NetlinkManager' + + def signal_term_handler(self, signal, frame): + log.info("NetlinkManager: Caught SIGTERM") + self.shutdown_flag = True + + def signal_int_handler(self, signal, frame): + log.info("NetlinkManager: Caught SIGINT") + self.shutdown_flag = True + + def shutdown(self): + if self.tx_socket: + self.tx_socket.close() + self.tx_socket = None + log.info("NetlinkManager: shutdown complete") + + def _debug_set_clear(self, msg_types, enabled): + """ + Enable or disable debugs for all msgs_types messages + """ + + for x in msg_types: + if enabled: + self.debug[x] = True + else: + if x in self.debug: + del self.debug[x] + + def debug_link(self, enabled): + self._debug_set_clear((RTM_NEWLINK, RTM_DELLINK, RTM_GETLINK, RTM_SETLINK), enabled) + + def debug_address(self, enabled): + self._debug_set_clear((RTM_NEWADDR, RTM_DELADDR, RTM_GETADDR), enabled) + + def debug_neighbor(self, enabled): + self._debug_set_clear((RTM_NEWNEIGH, RTM_DELNEIGH, RTM_GETNEIGH), enabled) + + def debug_route(self, enabled): + self._debug_set_clear((RTM_NEWROUTE, RTM_DELROUTE, RTM_GETROUTE), enabled) + + def debug_this_packet(self, mtype): + if mtype in self.debug: + return True + return False + + def tx_socket_allocate(self): + """ + The TX socket is used for install requests, sending RTM_GETXXXX + requests, etc + """ + self.tx_socket = socket.socket(socket.AF_NETLINK, socket.SOCK_RAW, 0) + self.tx_socket.bind((self.pid, 0)) + + def tx_nlpacket_raw(self, message): + """ + TX a bunch of concatenated nlpacket.messages....do NOT wait for an ACK + """ + if not self.tx_socket: + self.tx_socket_allocate() + self.tx_socket.sendall(message) + + def tx_nlpacket(self, nlpacket): + """ + TX a netlink packet but do NOT wait for an ACK + """ + if not nlpacket.message: + log.error('You must first call build_message() to create the packet') + return + + if not self.tx_socket: + self.tx_socket_allocate() + self.tx_socket.sendall(nlpacket.message) + + def tx_nlpacket_get_response(self, nlpacket): + + if not nlpacket.message: + log.error('You must first call build_message() to create the packet') + return + + if not self.tx_socket: + self.tx_socket_allocate() + self.tx_socket.sendall(nlpacket.message) + + # If debugs are enabled we will print the contents of the + # packet via the decode_packet call...so avoid printing + # two messages for one packet. + if not nlpacket.debug: + log.info("TXed %12s, pid %d, seq %d, %d bytes" % + (nlpacket.get_type_string(), nlpacket.pid, nlpacket.seq, nlpacket.length)) + + header_PACK = NetlinkPacket.header_PACK + header_LEN = NetlinkPacket.header_LEN + null_read = 0 + MAX_NULL_READS = 30 + msgs = [] + + # Now listen to our socket and wait for the reply + while True: + + if self.shutdown_flag: + log.info('shutdown flag is True, exiting') + return msgs + + # Only block for 1 second so we can wake up to see if self.shutdown_flag is True + (readable, writeable, exceptional) = select([self.tx_socket, ], [], [self.tx_socket, ], 1) + + if not readable: + null_read += 1 + + # Safety net to make sure we do not spend too much time in + # this while True loop + if null_read >= MAX_NULL_READS: + log.warning('Socket was not readable for %d attempts' % null_read) + return msgs + + continue + + for s in readable: + data = s.recv(4096) + + if not data: + log.info('RXed zero length data, the socket is closed') + return msgs + + while data: + + # Extract the length, etc from the header + (length, msgtype, flags, seq, pid) = unpack(header_PACK, data[:header_LEN]) + + debug_str = "RXed %12s, pid %d, seq %d, %d bytes" % (NetlinkPacket.type_to_string[msgtype], pid, seq, length) + + # This shouldn't happen but it would be nice to be aware of it if it does + if pid != nlpacket.pid: + log.debug(debug_str + '...we are not interested in this pid %s since ours is %s' % + (pid, nlpacket.pid)) + data = data[length:] + continue + if seq != nlpacket.seq: + log.debug(debug_str + '...we are not interested in this seq %s since ours is %s' % + (seq, nlpacket.seq)) + data = data[length:] + continue + # See if we RXed an ACK for our RTM_GETXXXX + if msgtype == NLMSG_DONE: + log.debug(debug_str + '...this is an ACK') + return msgs + + elif msgtype == NLMSG_ERROR: + + # The error code is a signed negative number. + error_code = abs(unpack('=i', data[header_LEN:header_LEN+4])[0]) + msg = Error(msgtype, nlpacket.debug) + msg.decode_packet(length, flags, seq, pid, data) + + debug_str += ", error code %s" % msg.error_to_string.get(error_code) + + # 0 is NLE_SUCCESS...everything else is a true error + if error_code: + if error_code == Error.NLE_NOADDR: + raise NetlinkNoAddressError(debug_str) + else: + raise NetlinkError(debug_str) + else: + log.info(debug_str + '...this is an ACK') + return msgs + + # No ACK...create a nlpacket object and append it to msgs + else: + + # If debugs are enabled we will print the contents of the + # packet via the decode_packet call...so avoid printing + # two messages for one packet. + if not nlpacket.debug: + log.info(debug_str) + + if msgtype == RTM_NEWLINK or msgtype == RTM_DELLINK: + msg = Link(msgtype, nlpacket.debug) + + elif msgtype == RTM_NEWADDR or msgtype == RTM_DELADDR: + msg = Address(msgtype, nlpacket.debug) + + elif msgtype == RTM_NEWNEIGH or msgtype == RTM_DELNEIGH: + msg = Neighbor(msgtype, nlpacket.debug) + + elif msgtype == RTM_NEWROUTE or msgtype == RTM_DELROUTE: + msg = Route(msgtype, nlpacket.debug) + + else: + raise Exception("RXed unknown netlink message type %s" % msgtype) + + msg.decode_packet(length, flags, seq, pid, data) + msgs.append(msg) + + data = data[length:] + + def ip_to_afi(self, ip): + type_ip = type(ip) + + if type_ip == IPv4Address: + return socket.AF_INET + elif type_ip == IPv6Address: + return socket.AF_INET6 + else: + raise Exception("%s is an invalid IP type" % type_ip) + + def request_dump(self, rtm_type, family, debug): + """ + Issue a RTM_GETROUTE, etc with the NLM_F_DUMP flag + set and return the results + """ + + if rtm_type == RTM_GETADDR: + msg = Address(rtm_type, debug) + msg.body = pack('Bxxxi', family, 0) + + elif rtm_type == RTM_GETLINK: + msg = Link(rtm_type, debug) + msg.body = pack('Bxxxiii', family, 0, 0, 0) + + elif rtm_type == RTM_GETNEIGH: + msg = Neighbor(rtm_type, debug) + msg.body = pack('Bxxxii', family, 0, 0) + + elif rtm_type == RTM_GETROUTE: + msg = Route(rtm_type, debug) + msg.body = pack('Bxxxii', family, 0, 0) + + else: + log.error("request_dump RTM_GET %s is not supported" % rtm_type) + return None + + msg.flags = NLM_F_REQUEST | NLM_F_DUMP + msg.attributes = {} + msg.build_message(self.sequence.next(), self.pid) + return self.tx_nlpacket_get_response(msg) + + # ====== + # Routes + # ====== + def _routes_add_or_delete(self, add_route, routes, ecmp_routes, table, protocol, route_scope, route_type): + + def tx_or_concat_message(total_message, route): + """ + Adding an ipv4 route only takes 60 bytes, if we are adding thousands + of them this can add up to a lot of send calls. Concat several of + them together before TXing. + """ + + if not total_message: + total_message = route.message + else: + total_message += route.message + + if len(total_message) >= PACKET_CONCAT_SIZE: + self.tx_nlpacket_raw(total_message) + total_message = None + + return total_message + + if add_route: + rtm_command = RTM_NEWROUTE + else: + rtm_command = RTM_DELROUTE + + total_message = None + PACKET_CONCAT_SIZE = 16384 + debug = rtm_command in self.debug + + if routes: + for (afi, ip, mask, nexthop, interface_index) in routes: + route = Route(rtm_command, debug) + route.flags = NLM_F_REQUEST | NLM_F_CREATE + route.body = pack('BBBBBBBBi', afi, mask, 0, 0, table, protocol, + route_scope, route_type, 0) + route.family = afi + route.add_attribute(Route.RTA_DST, ip) + if nexthop: + route.add_attribute(Route.RTA_GATEWAY, nexthop) + route.add_attribute(Route.RTA_OIF, interface_index) + route.build_message(self.sequence.next(), self.pid) + total_message = tx_or_concat_message(total_message, route) + + if total_message: + self.tx_nlpacket_raw(total_message) + + if ecmp_routes: + + for (route_key, value) in ecmp_routes.iteritems(): + (afi, ip, mask) = route_key + + route = Route(rtm_command, debug) + route.flags = NLM_F_REQUEST | NLM_F_CREATE + route.body = pack('BBBBBBBBi', afi, mask, 0, 0, table, protocol, + route_scope, route_type, 0) + route.family = afi + route.add_attribute(Route.RTA_DST, ip) + route.add_attribute(Route.RTA_MULTIPATH, value) + route.build_message(self.sequence.next(), self.pid) + total_message = tx_or_concat_message(total_message, route) + + if total_message: + self.tx_nlpacket_raw(total_message) + + def routes_add(self, routes, ecmp_routes, + table=Route.RT_TABLE_MAIN, + protocol=Route.RT_PROT_XORP, + route_scope=Route.RT_SCOPE_UNIVERSE, + route_type=Route.RTN_UNICAST): + self._routes_add_or_delete(True, routes, ecmp_routes, table, protocol, route_scope, route_type) + + def routes_del(self, routes, ecmp_routes, + table=Route.RT_TABLE_MAIN, + protocol=Route.RT_PROT_XORP, + route_scope=Route.RT_SCOPE_UNIVERSE, + route_type=Route.RTN_UNICAST): + self._routes_add_or_delete(False, routes, ecmp_routes, table, protocol, route_scope, route_type) + + def route_get(self, ip, debug=False): + """ + ip must be one of the following: + - IPv4Address + - IPv6Address + """ + # Transmit a RTM_GETROUTE to query for the route we want + route = Route(RTM_GETROUTE, debug) + route.flags = NLM_F_REQUEST | NLM_F_ACK + + # Set everything in the service header as 0 other than the afi + afi = self.ip_to_afi(ip) + route.body = pack('Bxxxxxxxi', afi, 0) + route.family = afi + route.add_attribute(Route.RTA_DST, ip) + route.build_message(self.sequence.next(), self.pid) + return self.tx_nlpacket_get_response(route) + + def routes_dump(self, family=socket.AF_UNSPEC, debug=True): + return self.request_dump(RTM_GETROUTE, family, debug) + + def routes_print(self, routes): + """ + Use tabulate to print a table of 'routes' + """ + header = ['Prefix', 'nexthop', 'ifindex'] + table = [] + + for x in routes: + if Route.RTA_DST not in x.attributes: + log.warning("Route is missing RTA_DST") + continue + + table.append(('%s/%d' % (x.attributes[Route.RTA_DST].value, x.src_len), + str(x.attributes[Route.RTA_GATEWAY].value) if Route.RTA_GATEWAY in x.attributes else None, + x.attributes[Route.RTA_OIF].value)) + + print tabulate(table, header, tablefmt='simple') + '\n' + + # ===== + # Links + # ===== + def _get_iface_by_name(self, ifname): + """ + Return a Link object for ifname + """ + debug = RTM_GETLINK in self.debug + + link = Link(RTM_GETLINK, debug) + link.flags = NLM_F_REQUEST | NLM_F_ACK + link.body = pack('=Bxxxiii', socket.AF_UNSPEC, 0, 0, 0) + link.add_attribute(Link.IFLA_IFNAME, ifname) + link.build_message(self.sequence.next(), self.pid) + + try: + return self.tx_nlpacket_get_response(link)[0] + + except NetlinkNoAddressError: + log.info("Netlink did not find interface %s" % ifname) + return None + + def get_iface_index(self, ifname): + """ + Return the interface index for ifname + """ + iface = self._get_iface_by_name(ifname) + + if iface: + return iface.ifindex + return None + + def _link_add(self, ifindex, ifname, kind, ifla_info_data): + """ + Build and TX a RTM_NEWLINK message to add the desired interface + """ + debug = RTM_NEWLINK in self.debug + + link = Link(RTM_NEWLINK, debug) + link.flags = NLM_F_CREATE | NLM_F_REQUEST + link.body = pack('Bxxxiii', socket.AF_UNSPEC, 0, 0, 0) + link.add_attribute(Link.IFLA_IFNAME, ifname) + link.add_attribute(Link.IFLA_LINK, ifindex) + link.add_attribute(Link.IFLA_LINKINFO, { + Link.IFLA_INFO_KIND: kind, + Link.IFLA_INFO_DATA: ifla_info_data + }) + link.build_message(self.sequence.next(), self.pid) + return self.tx_nlpacket(link) + + def link_add_vlan(self, ifindex, ifname, vlanid): + """ + ifindex is the index of the parent interface that this sub-interface + is being added to + """ + + ''' + If you name an interface swp2.17 but assign it to vlan 12, the kernel + will return a very misleading NLE_MSG_OVERFLOW error. It only does + this check if the ifname uses dot notation. + + Do this check here so we can provide a more intuitive error + ''' + if '.' in ifname: + ifname_vlanid = int(ifname.split('.')[-1]) + + if ifname_vlanid != vlanid: + raise InvalidInterfaceNameVlanCombo("Interface %s must belong " + "to VLAN %d (VLAN %d was requested)" % + (ifname, ifname_vlanid, vlanid)) + + return self._link_add(ifindex, ifname, 'vlan', {Link.IFLA_VLAN_ID: vlanid}) + + def link_add_macvlan(self, ifindex, ifname): + """ + ifindex is the index of the parent interface that this sub-interface + is being added to + """ + return self._link_add(ifindex, ifname, 'macvlan', {Link.IFLA_MACVLAN_MODE: Link.MACVLAN_MODE_PRIVATE}) + + def _link_bridge_vlan(self, msgtype, ifindex, vlanid, pvid, untagged, master): + """ + Build and TX a RTM_NEWLINK message to add the desired interface + """ + + if master: + flags = 0 + else: + flags = Link.BRIDGE_FLAGS_SELF + + if pvid: + vflags = Link.BRIDGE_VLAN_INFO_PVID | Link.BRIDGE_VLAN_INFO_UNTAGGED + elif untagged: + vflags = Link.BRIDGE_VLAN_INFO_UNTAGGED + else: + vflags = 0 + + debug = msgtype in self.debug + + link = Link(msgtype, debug) + link.flags = NLM_F_REQUEST | NLM_F_ACK + link.body = pack('Bxxxiii', socket.AF_BRIDGE, ifindex, 0, 0) + link.add_attribute(Link.IFLA_AF_SPEC, { + Link.IFLA_BRIDGE_FLAGS: flags, + Link.IFLA_BRIDGE_VLAN_INFO: (vflags, vlanid) + }) + link.build_message(self.sequence.next(), self.pid) + return self.tx_nlpacket(link) + + def link_add_bridge_vlan(self, ifindex, vlanid, pvid=False, untagged=False, master=False): + self._link_bridge_vlan(RTM_SETLINK, ifindex, vlanid, pvid, untagged, master) + + def link_del_bridge_vlan(self, ifindex, vlanid, pvid=False, untagged=False, master=False): + self._link_bridge_vlan(RTM_DELLINK, ifindex, vlanid, pvid, untagged, master) + + def link_set_updown(self, ifname, state): + """ + Either bring ifname up or take it down + """ + + if state == 'up': + if_flags = Link.IFF_UP + elif state == 'down': + if_flags = 0 + else: + raise Exception('Unsupported state %s, valid options are "up" and "down"' % state) + + debug = RTM_NEWLINK in self.debug + if_change = Link.IFF_UP + + link = Link(RTM_NEWLINK, debug) + link.flags = NLM_F_REQUEST + link.body = pack('=BxxxiLL', socket.AF_UNSPEC, 0, if_flags, if_change) + link.add_attribute(Link.IFLA_IFNAME, ifname) + link.build_message(self.sequence.next(), self.pid) + return self.tx_nlpacket(link) + + def link_set_protodown(self, ifname, state): + """ + Either bring ifname up or take it down by setting IFLA_PROTO_DOWN on or off + """ + flags = 0 + protodown = 1 if state == "on" else 0 + + debug = RTM_NEWLINK in self.debug + + link = Link(RTM_NEWLINK, debug) + link.flags = NLM_F_REQUEST + link.body = pack('=BxxxiLL', socket.AF_UNSPEC, 0, 0, 0) + link.add_attribute(Link.IFLA_IFNAME, ifname) + link.add_attribute(Link.IFLA_PROTO_DOWN, protodown) + link.build_message(self.sequence.next(), self.pid) + return self.tx_nlpacket(link) + + # ========= + # Neighbors + # ========= + def neighbor_add(self, afi, ifindex, ip, mac): + debug = RTM_NEWNEIGH in self.debug + service_hdr_flags = 0 + + nbr = Neighbor(RTM_NEWNEIGH, debug) + nbr.flags = NLM_F_CREATE | NLM_F_REQUEST + nbr.family = afi + nbr.body = pack('=BxxxiHBB', afi, ifindex, Neighbor.NUD_REACHABLE, service_hdr_flags, Route.RTN_UNICAST) + nbr.add_attribute(Neighbor.NDA_DST, ip) + nbr.add_attribute(Neighbor.NDA_LLADDR, mac) + nbr.build_message(self.sequence.next(), self.pid) + return self.tx_nlpacket(nbr) + + def neighbor_del(self, afi, ifindex, ip, mac): + debug = RTM_DELNEIGH in self.debug + service_hdr_flags = 0 + + nbr = Neighbor(RTM_DELNEIGH, debug) + nbr.flags = NLM_F_REQUEST + nbr.family = afi + nbr.body = pack('=BxxxiHBB', afi, ifindex, Neighbor.NUD_REACHABLE, service_hdr_flags, Route.RTN_UNICAST) + nbr.add_attribute(Neighbor.NDA_DST, ip) + nbr.add_attribute(Neighbor.NDA_LLADDR, mac) + nbr.build_message(self.sequence.next(), self.pid) + return self.tx_nlpacket(nbr) diff --git a/nlmanager/nlpacket.py b/nlmanager/nlpacket.py new file mode 100644 index 0000000..bce6044 --- /dev/null +++ b/nlmanager/nlpacket.py @@ -0,0 +1,2612 @@ +# Copyright (c) 2009-2013, Exa Networks Limited +# Copyright (c) 2009-2013, Thomas Mangin +# Copyright (c) 2015 Cumulus Networks, Inc. +# +# All rights reserved. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# The names of the Exa Networks Limited, Cumulus Networks, Inc. nor the names +# of its contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +# GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import logging +import struct +from ipaddr import IPv4Address, IPv6Address +from binascii import hexlify +from pprint import pformat +from socket import AF_INET, AF_INET6, AF_BRIDGE +from string import printable +from struct import pack, unpack, calcsize + +log = logging.getLogger(__name__) + +# Netlink message types +NLMSG_NOOP = 0x01 +NLMSG_ERROR = 0x02 +NLMSG_DONE = 0x03 +NLMSG_OVERRUN = 0x04 + +RTM_NEWLINK = 0x10 # Create a new network interface +RTM_DELLINK = 0x11 # Destroy a network interface +RTM_GETLINK = 0x12 # Retrieve information about a network interface(ifinfomsg) +RTM_SETLINK = 0x13 # + +RTM_NEWADDR = 0x14 +RTM_DELADDR = 0x15 +RTM_GETADDR = 0x16 + +RTM_NEWNEIGH = 0x1C +RTM_DELNEIGH = 0x1D +RTM_GETNEIGH = 0x1E + +RTM_NEWROUTE = 0x18 +RTM_DELROUTE = 0x19 +RTM_GETROUTE = 0x1A + +RTM_NEWQDISC = 0x24 +RTM_DELQDISC = 0x25 +RTM_GETQDISC = 0x26 + +# Netlink message flags +NLM_F_REQUEST = 0x01 # It is query message. +NLM_F_MULTI = 0x02 # Multipart message, terminated by NLMSG_DONE +NLM_F_ACK = 0x04 # Reply with ack, with zero or error code +NLM_F_ECHO = 0x08 # Echo this query + +# Modifiers to GET query +NLM_F_ROOT = 0x100 # specify tree root +NLM_F_MATCH = 0x200 # return all matching +NLM_F_DUMP = NLM_F_ROOT | NLM_F_MATCH +NLM_F_ATOMIC = 0x400 # atomic GET + +# Modifiers to NEW query +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 + +NLA_F_NESTED = 0x8000 +NLA_F_NET_BYTEORDER = 0x4000 +NLA_TYPE_MASK = ~(NLA_F_NESTED | NLA_F_NET_BYTEORDER) + +# Groups +RTMGRP_LINK = 0x1 +RTMGRP_NOTIFY = 0x2 +RTMGRP_NEIGH = 0x4 +RTMGRP_TC = 0x8 +RTMGRP_IPV4_IFADDR = 0x10 +RTMGRP_IPV4_MROUTE = 0x20 +RTMGRP_IPV4_ROUTE = 0x40 +RTMGRP_IPV4_RULE = 0x80 +RTMGRP_IPV6_IFADDR = 0x100 +RTMGRP_IPV6_MROUTE = 0x200 +RTMGRP_IPV6_ROUTE = 0x400 +RTMGRP_IPV6_IFINFO = 0x800 +RTMGRP_DECnet_IFADDR = 0x1000 +RTMGRP_DECnet_ROUTE = 0x4000 +RTMGRP_IPV6_PREFIX = 0x20000 + +RTMGRP_ALL = (RTMGRP_LINK | RTMGRP_NOTIFY | RTMGRP_NEIGH | RTMGRP_TC | + RTMGRP_IPV4_IFADDR | RTMGRP_IPV4_MROUTE | RTMGRP_IPV4_ROUTE | RTMGRP_IPV4_RULE | + RTMGRP_IPV6_IFADDR | RTMGRP_IPV6_MROUTE | RTMGRP_IPV6_ROUTE | RTMGRP_IPV6_IFINFO | + RTMGRP_DECnet_IFADDR | RTMGRP_DECnet_ROUTE | + RTMGRP_IPV6_PREFIX) + +# Colors for logging +red = 91 +green = 92 +yellow = 93 +blue = 94 + + +def zfilled_hex(value, digits): + return '0x' + hex(value)[2:].zfill(digits) + + +def remove_trailing_null(line): + """ + Remove the last character if it is a NULL...having that NULL + causes python to print a garbage character + """ + + if ord(line[-1]) == 0: + line = line[:-1] + + return line + + +def mac_int_to_str(mac_int): + """ + Return an integer in MAC string format + """ + + # [2:] to remove the leading 0x, then fill out to 12 zeroes, then uppercase + all_caps = hex(int(mac_int))[2:].zfill(12).upper() + + if all_caps[-1] == 'L': + all_caps = all_caps[:-1] + all_caps = all_caps.zfill(12).upper() + + return "%s.%s.%s" % (all_caps[0:4], all_caps[4:8], all_caps[8:12]) + + +def data_to_color_text(line_number, color, data, extra=''): + (c1, c2, c3, c4) = unpack('BBBB', data[0:4]) + in_ascii = [] + + for c in (c1, c2, c3, c4): + char_c = chr(c) + + if char_c in printable[:-5]: + in_ascii.append(char_c) + else: + in_ascii.append('.') + + return ' %2d: \033[%dm0x%02x%02x%02x%02x\033[0m %s %s' % (line_number, color, c1, c2, c3, c4, ''.join(in_ascii), extra) + + +def padded_length(length): + return int((length + 3) / 4) * 4 + + +class Attribute(object): + + def __init__(self, atype, string, logger): + self.atype = atype + self.string = string + self.HEADER_PACK = '=HH' + self.HEADER_LEN = calcsize(self.HEADER_PACK) + self.PACK = None + self.LEN = None + self.value = None + self.nested = False + self.net_byteorder = False + self.log = logger + + def __str__(self): + return self.string + + def pad_bytes_needed(self, length): + """ + Return the number of bytes that should be added to align on a 4-byte boundry + """ + remainder = length % 4 + + if remainder: + return 4 - remainder + + return 0 + + def pad(self, length, raw): + pad = self.pad_bytes_needed(length) + + if pad: + raw += '\0' * pad + + return raw + + def encode(self): + length = self.HEADER_LEN + self.LEN + attr_type_with_flags = self.atype + + if self.nested: + attr_type_with_flags = attr_type_with_flags | NLA_F_NESTED + + if self.net_byteorder: + attr_type_with_flags = attr_type_with_flags | NLA_F_NET_BYTEORDER + + raw = pack(self.HEADER_PACK, length, attr_type_with_flags) + pack(self.PACK, self.value) + raw = self.pad(length, raw) + return raw + + def decode_length_type(self, data): + """ + The first two bytes of an attribute are the length, the next two bytes are the type + """ + self.data = data + prev_atype = self.atype + (data1, data2) = unpack(self.HEADER_PACK, data[:self.HEADER_LEN]) + self.length = int(data1) + self.atype = int(data2) + self.attr_end = padded_length(self.length) + + self.nested = True if self.atype & NLA_F_NESTED else False + self.net_byteorder = True if self.atype & NLA_F_NET_BYTEORDER else False + self.atype = self.atype & NLA_TYPE_MASK + + # Should never happen + assert self.atype == prev_atype, "This object changes attribute type from %d to %d, this is bad" % (prev_atype, self.atype) + + def dump_first_line(self, dump_buffer, line_number, color): + """ + Add the "Length....Type..." line to the dump buffer + """ + if self.attr_end == self.length: + padded_to = ', ' + else: + padded_to = ' padded to %d, ' % self.attr_end + + extra = 'Length %s (%d)%sType %s%s%s (%d) %s' % \ + (zfilled_hex(self.length, 4), self.length, + padded_to, + zfilled_hex(self.atype, 4), + " (NLA_F_NESTED set)" if self.nested else "", + " (NLA_F_NET_BYTEORDER set)" if self.net_byteorder else "", + self.atype, + self) + + dump_buffer.append(data_to_color_text(line_number, color, self.data[0:4], extra)) + return line_number + 1 + + def dump_lines(self, dump_buffer, line_number, color): + line_number = self.dump_first_line(dump_buffer, line_number, color) + + for x in xrange(1, self.attr_end/4): + start = x * 4 + end = start + 4 + dump_buffer.append(data_to_color_text(line_number, color, self.data[start:end], '')) + line_number += 1 + + return line_number + + def get_pretty_value(self): + return self.value + + +class AttributeFourByteValue(Attribute): + + def __init__(self, atype, string, logger): + Attribute.__init__(self, atype, string, logger) + self.PACK = '=L' + self.LEN = calcsize(self.PACK) + + def decode(self, parent_msg, data): + self.decode_length_type(data) + assert self.attr_end == 8, "Attribute length for %s must be 8, it is %d" % (self, self.attr_end) + + try: + self.value = int(unpack(self.PACK, self.data[4:])[0]) + except struct.error: + self.log.error("%s unpack of %s failed, data 0x%s" % (self, self.PACK, hexlify(self.data[4:]))) + raise + + def dump_lines(self, dump_buffer, line_number, color): + line_number = self.dump_first_line(dump_buffer, line_number, color) + dump_buffer.append(data_to_color_text(line_number, color, self.data[4:8], self.value)) + return line_number + 1 + + +class AttributeString(Attribute): + + def __init__(self, atype, string, logger): + Attribute.__init__(self, atype, string, logger) + self.PACK = None + self.LEN = None + + def encode(self): + # some interface names come from JSON unicode strings + # and cannot be packed as is so we must convert them to strings + if isinstance(self.value, unicode): + self.value = str(self.value) + self.PACK = '%ds' % len(self.value) + self.LEN = calcsize(self.PACK) + + length = self.HEADER_LEN + self.LEN + raw = pack(self.HEADER_PACK, length, self.atype) + pack(self.PACK, self.value) + raw = self.pad(length, raw) + return raw + + def decode(self, parent_msg, data): + self.decode_length_type(data) + self.PACK = '%ds' % (self.length - 4) + self.LEN = calcsize(self.PACK) + + try: + self.value = remove_trailing_null(unpack(self.PACK, self.data[4:self.length])[0]) + except struct.error: + self.log.error("%s unpack of %s failed, data 0x%s" % (self, self.PACK, hexlify(self.data[4:self.length]))) + raise + + +class AttributeIPAddress(Attribute): + + def __init__(self, atype, string, family, logger): + Attribute.__init__(self, atype, string, logger) + self.value_int = None + self.value_int_str = None + self.family = family + + if self.family == AF_INET: + self.PACK = '>L' + + elif self.family == AF_INET6: + self.PACK = '>QQ' + + elif self.family == AF_BRIDGE: + self.PACK = '>L' + + else: + raise Exception("%s is not a supported address family" % self.family) + + self.LEN = calcsize(self.PACK) + + def decode(self, parent_msg, data): + self.decode_length_type(data) + + try: + if self.family == AF_INET: + self.value = IPv4Address(unpack(self.PACK, self.data[4:])[0]) + + elif self.family == AF_INET6: + (data1, data2) = unpack(self.PACK, self.data[4:]) + self.value = IPv6Address(data1 << 64 | data2) + + elif self.family == AF_BRIDGE: + self.value = unpack(self.PACK, self.data[4:])[0] + + self.value_int = int(self.value) + self.value_int_str = str(self.value_int) + + except struct.error: + self.value = None + self.value_int = None + self.value_int_str = None + self.log.error("%s unpack of %s failed, data 0x%s" % (self, self.PACK, hexlify(self.data[4:]))) + raise + + def dump_lines(self, dump_buffer, line_number, color): + line_number = self.dump_first_line(dump_buffer, line_number, color) + + if self.family == AF_INET: + dump_buffer.append(data_to_color_text(line_number, color, self.data[4:8], self.value)) + line_number += 1 + + elif self.family == AF_INET6: + + for x in xrange(1, self.attr_end/4): + start = x * 4 + end = start + 4 + dump_buffer.append(data_to_color_text(line_number, color, self.data[start:end], self.value)) + line_number += 1 + + elif self.family == AF_BRIDGE: + dump_buffer.append(data_to_color_text(line_number, color, self.data[4:8], self.value)) + line_number += 1 + + return line_number + + +class AttributeMACAddress(Attribute): + + def __init__(self, atype, string, logger): + Attribute.__init__(self, atype, string, logger) + self.PACK = '>LHxx' + self.LEN = calcsize(self.PACK) + + def decode(self, parent_msg, data): + self.decode_length_type(data) + + try: + (data1, data2) = unpack(self.PACK, self.data[4:]) + self.value = mac_int_to_str(data1 << 16 | data2) + + except struct.error: + self.log.error("%s unpack of %s failed, data 0x%s" % (self, self.PACK, hexlify(self.data[4:]))) + raise + + def encode(self): + length = self.HEADER_LEN + self.LEN + mac_raw = int(self.value.replace('.', '').replace(':', ''), 16) + raw = pack(self.HEADER_PACK, length, self.atype) + pack(self.PACK, mac_raw >> 16, mac_raw & 0x0000FF) + raw = self.pad(length, raw) + return raw + + def dump_lines(self, dump_buffer, line_number, color): + line_number = self.dump_first_line(dump_buffer, line_number, color) + dump_buffer.append(data_to_color_text(line_number, color, self.data[4:8], self.value)) + dump_buffer.append(data_to_color_text(line_number, color, self.data[8:12], self.value)) + return line_number + 1 + + +class AttributeGeneric(Attribute): + + def __init__(self, atype, string, logger): + Attribute.__init__(self, atype, string, logger) + self.PACK = None + self.LEN = None + + def decode(self, parent_msg, data): + self.decode_length_type(data) + wordcount = (self.attr_end - 4)/4 + self.PACK = '=%dL' % wordcount + self.LEN = calcsize(self.PACK) + + try: + self.value = ''.join(map(str, unpack(self.PACK, self.data[4:]))) + except struct.error: + self.log.error("%s unpack of %s failed, data 0x%s" % (self, self.PACK, hexlify(self.data[4:]))) + raise + + +class AttributeIFLA_AF_SPEC(Attribute): + """ + value will be a dictionary such as: + { + Link.IFLA_BRIDGE_FLAGS: flags, + Link.IFLA_BRIDGE_VLAN_INFO: (vflags, vlanid) + } + """ + + def encode(self): + pack_layout = [self.HEADER_PACK] + payload = [0, self.atype] + attr_length_index = 0 + + # For now this assumes that all data will be packed in the native endian + # order (=). If a field is added that needs to be packed via network + # order (>) then some smarts will need to be added to split the pack_layout + # string at the >, split the payload and make the needed pack() calls. + # + # Until we cross that bridge though we will keep things nice and simple and + # pack everything via a single pack() call. + for (sub_attr_type, sub_attr_value) in self.value.iteritems(): + sub_attr_pack_layout = ['=', 'HH'] + sub_attr_payload = [0, sub_attr_type] + sub_attr_length_index = 0 + + if sub_attr_type == Link.IFLA_BRIDGE_FLAGS: + sub_attr_pack_layout.append('H') + sub_attr_payload.append(sub_attr_value) + + elif sub_attr_type == Link.IFLA_BRIDGE_VLAN_INFO: + sub_attr_pack_layout.append('HH') + sub_attr_payload.append(sub_attr_value[0]) + sub_attr_payload.append(sub_attr_value[1]) + + else: + self.log.debug('Add support for encoding IFLA_AF_SPEC sub-attribute type %d' % sub_attr_type) + continue + + sub_attr_length = calcsize(''.join(sub_attr_pack_layout)) + sub_attr_payload[sub_attr_length_index] = sub_attr_length + + # add padding + for x in xrange(self.pad_bytes_needed(sub_attr_length)): + sub_attr_pack_layout.append('x') + + # The [1:] is to remove the leading = so that when we do the ''.join() later + # we do not end up with an = in the middle of the pack layout string. There + # will be an = at the beginning via self.HEADER_PACK + sub_attr_pack_layout = sub_attr_pack_layout[1:] + + # Now extend the ovarall attribute pack_layout/payload to include this sub-attribute + pack_layout.extend(sub_attr_pack_layout) + payload.extend(sub_attr_payload) + + pack_layout = ''.join(pack_layout) + + # Fill in the length field + length = calcsize(pack_layout) + payload[attr_length_index] = length + + raw = pack(pack_layout, *payload) + raw = self.pad(length, raw) + return raw + + def decode(self, parent_msg, data): + """ + value is a dictionary such as: + { + Link.IFLA_BRIDGE_FLAGS: flags, + Link.IFLA_BRIDGE_VLAN_INFO: (vflags, vlanid) + } + """ + self.decode_length_type(data) + self.value = {} + + data = self.data[4:] + + while data: + (sub_attr_length, sub_attr_type) = unpack('=HH', data[:4]) + sub_attr_end = padded_length(sub_attr_length) + + if not sub_attr_length: + self.log.error('parsed a zero length sub-attr') + return + + sub_attr_data = data[4:sub_attr_end] + + if sub_attr_type == Link.IFLA_BRIDGE_FLAGS: + self.value[Link.IFLA_BRIDGE_FLAGS] = unpack("=H", sub_attr_data[0:2])[0] + + elif sub_attr_type == Link.IFLA_BRIDGE_VLAN_INFO: + self.value[Link.IFLA_INFO_DATA] = tuple(unpack("=HH", sub_attr_data[0:4])) + + else: + self.log.debug('Add support for decoding IFLA_AF_SPEC sub-attribute type %s (%d), length %d, padded to %d' % + (parent_msg.get_ifla_bridge_af_spec_to_string(sub_attr_type), sub_attr_type, sub_attr_length, sub_attr_end)) + + data = data[sub_attr_end:] + + def dump_lines(self, dump_buffer, line_number, color): + line_number = self.dump_first_line(dump_buffer, line_number, color) + extra = '' + + next_sub_attr_line = 0 + sub_attr_line = True + + for x in xrange(1, self.attr_end/4): + start = x * 4 + end = start + 4 + + if line_number == next_sub_attr_line: + sub_attr_line = True + + if sub_attr_line: + sub_attr_line = False + + (sub_attr_length, sub_attr_type) = unpack('=HH', self.data[start:start+4]) + sub_attr_end = padded_length(sub_attr_length) + + next_sub_attr_line = line_number + (sub_attr_end/4) + + if sub_attr_end == sub_attr_length: + padded_to = ', ' + else: + padded_to = ' padded to %d, ' % sub_attr_end + + extra = 'Nested Attribute - Length %s (%d)%s Type %s (%d) %s' % \ + (zfilled_hex(sub_attr_length, 4), sub_attr_length, + padded_to, + zfilled_hex(sub_attr_type, 4), sub_attr_type, + Link.ifla_bridge_af_spec_to_string.get(sub_attr_type)) + else: + extra = '' + + dump_buffer.append(data_to_color_text(line_number, color, self.data[start:end], extra)) + line_number += 1 + + return line_number + + def get_pretty_value(self): + # We do this so we can print a more human readable dictionary + # with the names of the nested keys instead of their numbers + value_pretty = {} + + for (sub_key, sub_value) in self.value.iteritems(): + sub_key_pretty = "(%2d) % s" % (sub_key, Link.ifla_bridge_af_spec_to_string.get(sub_key)) + value_pretty[sub_key_pretty] = sub_value + + return value_pretty + + + +class AttributeRTA_MULTIPATH(Attribute): + """ +/* RTA_MULTIPATH --- array of struct rtnexthop. + * + * "struct rtnexthop" describes all necessary nexthop information, + * i.e. parameters of path to a destination via this nexthop. + * + * At the moment it is impossible to set different prefsrc, mtu, window + * and rtt for different paths from multipath. + */ + +struct rtnexthop { + unsigned short rtnh_len; + unsigned char rtnh_flags; + unsigned char rtnh_hops; + int rtnh_ifindex; +}; + """ + + def __init__(self, atype, string, family, logger): + Attribute.__init__(self, atype, string, logger) + self.family = family + self.PACK = None + self.LEN = None + self.RTNH_PACK = '=HBBL' # rtnh_len, flags, hops, ifindex + self.RTNH_LEN = calcsize(self.RTNH_PACK) + self.IPV4_LEN = 4 + self.IPV6_LEN = 16 + + def encode(self): + + # Calculate the length + if self.family == AF_INET: + ip_len = self.IPV4_LEN + elif self.family == AF_INET6: + ip_len = self.IPV6_LEN + + # Attribute header + length = self.HEADER_LEN + ((self.RTNH_LEN + self.HEADER_LEN + ip_len) * len(self.value)) + raw = pack(self.HEADER_PACK, length, self.atype) + + rtnh_flags = 0 + rtnh_hops = 0 + rtnh_len = self.RTNH_LEN + self.HEADER_LEN + ip_len + + for (nexthop, rtnh_ifindex) in self.value: + + # rtnh structure + raw += pack(self.RTNH_PACK, rtnh_len, rtnh_flags, rtnh_hops, rtnh_ifindex) + + # Gateway + raw += pack(self.HEADER_PACK, self.HEADER_LEN + ip_len, Route.RTA_GATEWAY) + + if self.family == AF_INET: + raw += pack('>L', nexthop) + elif self.family == AF_INET6: + raw += pack('>QQ', nexthop >> 64, nexthop & 0x0000000000000000FFFFFFFFFFFFFFFF) + + raw = self.pad(length, raw) + return raw + + def decode(self, parent_msg, data): + self.decode_length_type(data) + self.value = [] + + data = self.data[4:] + + while data: + (rtnh_len, rtnh_flags, rtnh_hops, rtnh_ifindex) = unpack(self.RTNH_PACK, data[:self.RTNH_LEN]) + data = data[self.RTNH_LEN:] + + (attr_type, attr_length) = unpack(self.HEADER_PACK, self.data[:self.HEADER_LEN]) + data = data[self.HEADER_LEN:] + + if self.family == AF_INET: + nexthop = IPv4Address(unpack('>L', data[:self.IPV4_LEN])[0]) + self.value.append((nexthop, rtnh_ifindex, rtnh_flags, rtnh_hops)) + data = data[self.IPV4_LEN:] + + elif self.family == AF_INET6: + (data1, data2) = unpack('>QQ', data[:self.IPV6_LEN]) + nexthop = IPv6Address(data1 << 64 | data2) + self.value.append((nexthop, rtnh_ifindex, rtnh_flags, rtnh_hops)) + data = data[self.IPV6_LEN:] + + self.value = tuple(self.value) + + +class AttributeIFLA_LINKINFO(Attribute): + """ + value is a dictionary such as: + + { + Link.IFLA_INFO_KIND : 'vlan', + Link.IFLA_INFO_DATA : { + Link.IFLA_VLAN_ID : vlanid, + } + } + """ + def encode(self): + pack_layout = [self.HEADER_PACK] + payload = [0, self.atype] + attr_length_index = 0 + + kind = self.value[Link.IFLA_INFO_KIND] + + if kind not in ('vlan', 'macvlan'): + raise Exception('Unsupported IFLA_INFO_KIND %s' % kind) + + # For now this assumes that all data will be packed in the native endian + # order (=). If a field is added that needs to be packed via network + # order (>) then some smarts will need to be added to split the pack_layout + # string at the >, split the payload and make the needed pack() calls. + # + # Until we cross that bridge though we will keep things nice and simple and + # pack everything via a single pack() call. + for (sub_attr_type, sub_attr_value) in self.value.iteritems(): + sub_attr_pack_layout = ['=', 'HH'] + sub_attr_payload = [0, sub_attr_type] + sub_attr_length_index = 0 + + if sub_attr_type == Link.IFLA_INFO_KIND: + sub_attr_pack_layout.append('%ds' % len(sub_attr_value)) + sub_attr_payload.append(sub_attr_value) + + elif sub_attr_type == Link.IFLA_INFO_DATA: + + for (info_data_type, info_data_value) in sub_attr_value.iteritems(): + + if kind == 'vlan': + if info_data_type == Link.IFLA_VLAN_ID: + sub_attr_pack_layout.append('HH') + sub_attr_payload.append(6) # length + sub_attr_payload.append(info_data_type) + + # The vlan-id + sub_attr_pack_layout.append('H') + sub_attr_payload.append(info_data_value) + + # pad 2 bytes + sub_attr_pack_layout.extend('xx') + + else: + self.log.debug('Add support for encoding IFLA_INFO_DATA vlan sub-attribute type %d' % info_data_type) + + elif kind == 'macvlan': + if info_data_type == Link.IFLA_MACVLAN_MODE: + sub_attr_pack_layout.append('HH') + sub_attr_payload.append(8) # length + sub_attr_payload.append(info_data_type) + + # macvlan mode + sub_attr_pack_layout.append('L') + sub_attr_payload.append(info_data_value) + + else: + self.log.debug('Add support for encoding IFLA_INFO_DATA macvlan sub-attribute type %d' % info_data_type) + + else: + self.log.debug('Add support for encoding IFLA_LINKINFO sub-attribute type %d' % sub_attr_type) + continue + + sub_attr_length = calcsize(''.join(sub_attr_pack_layout)) + sub_attr_payload[sub_attr_length_index] = sub_attr_length + + # add padding + for x in xrange(self.pad_bytes_needed(sub_attr_length)): + sub_attr_pack_layout.append('x') + + # The [1:] is to remove the leading = so that when we do the ''.join() later + # we do not end up with an = in the middle of the pack layout string. There + # will be an = at the beginning via self.HEADER_PACK + sub_attr_pack_layout = sub_attr_pack_layout[1:] + + # Now extend the ovarall attribute pack_layout/payload to include this sub-attribute + pack_layout.extend(sub_attr_pack_layout) + payload.extend(sub_attr_payload) + + pack_layout = ''.join(pack_layout) + + # Fill in the length field + length = calcsize(pack_layout) + payload[attr_length_index] = length + + raw = pack(pack_layout, *payload) + raw = self.pad(length, raw) + return raw + + def decode(self, parent_msg, data): + """ + value is a dictionary such as: + + { + Link.IFLA_INFO_KIND : 'vlan', + Link.IFLA_INFO_DATA : { + Link.IFLA_VLAN_ID : vlanid, + } + } + """ + self.decode_length_type(data) + self.value = {} + + data = self.data[4:] + + # IFLA_MACVLAN_MODE and IFLA_VLAN_ID both have a value of 1 and both are + # valid IFLA_INFO_DATA entries :( The sender must TX IFLA_INFO_KIND + # first in order for us to know if "1" is IFLA_MACVLAN_MODE vs IFLA_VLAN_ID. + while data: + (sub_attr_length, sub_attr_type) = unpack('=HH', data[:4]) + sub_attr_end = padded_length(sub_attr_length) + + if not sub_attr_length: + self.log.error('parsed a zero length sub-attr') + return + + if sub_attr_type == Link.IFLA_INFO_KIND: + self.value[Link.IFLA_INFO_KIND] = remove_trailing_null(unpack('%ds' % (sub_attr_length - 4), data[4:sub_attr_length])[0]) + + elif sub_attr_type == Link.IFLA_INFO_DATA: + + sub_attr_data = data[4:sub_attr_end] + self.value[Link.IFLA_INFO_DATA] = {} + + while sub_attr_data: + (info_data_length, info_data_type) = unpack('=HH', sub_attr_data[:4]) + info_data_end = padded_length(info_data_length) + # self.log.info('sub attr length %d, end %d, type %d' % (info_data_length, info_data_end, info_data_type)) + + if not sub_attr_data: + self.log.error('RXed zero length sub-attribute') + break + + if Link.IFLA_INFO_KIND not in self.value: + self.log.warning('IFLA_INFO_KIND is not known...we cannot parse IFLA_INFO_DATA') + + elif self.value[Link.IFLA_INFO_KIND] == 'vlan': + if info_data_type == Link.IFLA_VLAN_ID: + self.value[Link.IFLA_INFO_DATA][info_data_type] = unpack('=H', sub_attr_data[4:6])[0] + else: + self.log.debug('Add support for decoding IFLA_INFO_KIND vlan type %s (%d), length %d, padded to %d' % + (parent_msg.get_ifla_vlan_string(info_data_type), info_data_type, info_data_length, info_data_end)) + + elif self.value[Link.IFLA_INFO_KIND] == 'macvlan': + if info_data_type == Link.IFLA_MACVLAN_MODE: + self.value[Link.IFLA_INFO_DATA][info_data_type] = unpack('=L', sub_attr_data[4:8])[0] + else: + self.log.debug('Add support for decoding IFLA_INFO_KIND macvlan type %s (%d), length %d, padded to %d' % + (parent_msg.get_ifla_macvlan_string(info_data_type), info_data_type, info_data_length, info_data_end)) + + elif self.value[Link.IFLA_INFO_KIND] == 'vxlan': + + # IPv4Address + if info_data_type in (Link.IFLA_VXLAN_GROUP, + Link.IFLA_VXLAN_LOCAL): + self.value[Link.IFLA_INFO_DATA][info_data_type] = IPv4Address(unpack('>L', sub_attr_data[4:8])[0]) + + # 4-byte int + elif info_data_type in (Link.IFLA_VXLAN_ID, + Link.IFLA_VXLAN_LINK, + Link.IFLA_VXLAN_AGEING, + Link.IFLA_VXLAN_LIMIT, + Link.IFLA_VXLAN_PORT_RANGE): + self.value[Link.IFLA_INFO_DATA][info_data_type] = unpack('=L', sub_attr_data[4:8])[0] + + # 2-byte int + elif info_data_type in (Link.IFLA_VXLAN_PORT, ): + self.value[Link.IFLA_INFO_DATA][info_data_type] = unpack('=H', sub_attr_data[4:6])[0] + + # 1-byte int + elif info_data_type in (Link.IFLA_VXLAN_TTL, + Link.IFLA_VXLAN_TOS, + Link.IFLA_VXLAN_LEARNING, + Link.IFLA_VXLAN_PROXY, + Link.IFLA_VXLAN_RSC, + Link.IFLA_VXLAN_L2MISS, + Link.IFLA_VXLAN_L3MISS, + Link.IFLA_VXLAN_UDP_CSUM, + Link.IFLA_VXLAN_UDP_ZERO_CSUM6_TX, + Link.IFLA_VXLAN_UDP_ZERO_CSUM6_RX, + Link.IFLA_VXLAN_REMCSUM_TX, + Link.IFLA_VXLAN_REMCSUM_RX, + Link.IFLA_VXLAN_REPLICATION_TYPE): + self.value[Link.IFLA_INFO_DATA][info_data_type] = unpack('=B', sub_attr_data[4])[0] + + else: + # sub_attr_end = padded_length(sub_attr_length) + self.log.debug('Add support for decoding IFLA_INFO_KIND vxlan type %s (%d), length %d, padded to %d' % + (parent_msg.get_ifla_vxlan_string(info_data_type), info_data_type, info_data_length, info_data_end)) + + elif self.value[Link.IFLA_INFO_KIND] == 'bond': + self.log.debug('Add support for decoding IFLA_INFO_KIND bond type %s (%d), length %d, padded to %d' % + (parent_msg.get_ifla_bond_string(info_data_type), info_data_type, info_data_length, info_data_end)) + + elif self.value[Link.IFLA_INFO_KIND] == 'bridge': + + if info_data_type in (Link.IFLA_BRPORT_STATE, + Link.IFLA_BRPORT_PRIORITY, + Link.IFLA_BRPORT_COST): + self.value[Link.IFLA_INFO_DATA][info_data_type] = unpack('=L', sub_attr_data[4:8])[0] + + elif info_data_type in (Link.IFLA_BRPORT_FAST_LEAVE, ): + self.value[Link.IFLA_INFO_DATA][info_data_type] = unpack('=B', sub_attr_data[4])[0] + + else: + self.log.debug('Add support for decoding IFLA_INFO_KIND bridge type %s (%d), length %d, padded to %d' % + (parent_msg.get_ifla_bridge_string(info_data_type), info_data_type, info_data_length, info_data_end)) + + else: + self.log.debug("Add support for decoding IFLA_INFO_KIND %s (%d), length %d, padded to %d" % + (self.value[Link.IFLA_INFO_KIND], info_data_type, info_data_length, info_data_end)) + + sub_attr_data = sub_attr_data[info_data_end:] + + elif sub_attr_type == Link.IFLA_INFO_SLAVE_KIND: + self.value[Link.IFLA_INFO_SLAVE_KIND] = remove_trailing_null(unpack('%ds' % (sub_attr_length - 4), data[4:sub_attr_length])[0]) + + else: + self.log.debug('Add support for decoding IFLA_LINKINFO sub-attribute type %s (%d), length %d, padded to %d' % + (parent_msg.get_ifla_info_string(sub_attr_type), sub_attr_type, sub_attr_length, sub_attr_end)) + + data = data[sub_attr_end:] + + # self.log.info('IFLA_LINKINFO values %s' % pformat(self.value)) + + def dump_lines(self, dump_buffer, line_number, color): + line_number = self.dump_first_line(dump_buffer, line_number, color) + extra = '' + + next_sub_attr_line = 0 + sub_attr_line = True + + for x in xrange(1, self.attr_end/4): + start = x * 4 + end = start + 4 + + if line_number == next_sub_attr_line: + sub_attr_line = True + + if sub_attr_line: + sub_attr_line = False + + (sub_attr_length, sub_attr_type) = unpack('=HH', self.data[start:start+4]) + sub_attr_end = padded_length(sub_attr_length) + + next_sub_attr_line = line_number + (sub_attr_end/4) + + if sub_attr_end == sub_attr_length: + padded_to = ', ' + else: + padded_to = ' padded to %d, ' % sub_attr_end + + extra = 'Nested Attribute - Length %s (%d)%s Type %s (%d) %s' % \ + (zfilled_hex(sub_attr_length, 4), sub_attr_length, + padded_to, + zfilled_hex(sub_attr_type, 4), sub_attr_type, + Link.ifla_info_to_string.get(sub_attr_type)) + else: + extra = '' + + dump_buffer.append(data_to_color_text(line_number, color, self.data[start:end], extra)) + line_number += 1 + + return line_number + + def get_pretty_value(self): + value_pretty = self.value + ifla_info_kind = self.value.get(Link.IFLA_INFO_KIND) + + # We do this so we can print a more human readable dictionary + # with the names of the nested keys instead of their numbers + + # Most of these are placeholders...we need to add support + # for more human readable dictionaries for bond, bridge, etc + if ifla_info_kind == 'bond': + pass + + elif ifla_info_kind == 'bridge': + pass + + elif ifla_info_kind == 'macvlan': + pass + + elif ifla_info_kind == 'vlan': + pass + + elif ifla_info_kind == 'vxlan': + value_pretty = {} + + for (sub_key, sub_value) in self.value.iteritems(): + sub_key_pretty = "(%2d) % s" % (sub_key, Link.ifla_info_to_string[sub_key]) + sub_value_pretty = sub_value + + if sub_key == Link.IFLA_INFO_DATA: + sub_value_pretty = {} + + for (sub_sub_key, sub_sub_value) in sub_value.iteritems(): + sub_sub_key_pretty = "(%2d) %s" % (sub_sub_key, Link.ifla_vxlan_to_string[sub_sub_key]) + sub_value_pretty[sub_sub_key_pretty] = sub_sub_value + + value_pretty[sub_key_pretty] = sub_value_pretty + + return value_pretty + + +class NetlinkPacket(object): + """ + Netlink Header + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Type | Flags | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Sequence Number | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Process ID (PID) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + """ + + header_PACK = 'IHHII' + header_LEN = calcsize(header_PACK) + + # Netlink packet types + # /usr/include/linux/rtnetlink.h + type_to_string = { + NLMSG_NOOP : 'NLMSG_NOOP', + NLMSG_ERROR : 'NLMSG_ERROR', + NLMSG_DONE : 'NLMSG_DONE', + NLMSG_OVERRUN : 'NLMSG_OVERRUN', + RTM_NEWLINK : 'RTM_NEWLINK', + RTM_DELLINK : 'RTM_DELLINK', + RTM_GETLINK : 'RTM_GETLINK', + RTM_SETLINK : 'RTM_SETLINK', + RTM_NEWADDR : 'RTM_NEWADDR', + RTM_DELADDR : 'RTM_DELADDR', + RTM_GETADDR : 'RTM_GETADDR', + RTM_NEWNEIGH : 'RTM_NEWNEIGH', + RTM_DELNEIGH : 'RTM_DELNEIGH', + RTM_GETNEIGH : 'RTM_GETNEIGH', + RTM_NEWROUTE : 'RTM_NEWROUTE', + RTM_DELROUTE : 'RTM_DELROUTE', + RTM_GETROUTE : 'RTM_GETROUTE', + RTM_NEWQDISC : 'RTM_NEWQDISC', + RTM_DELQDISC : 'RTM_DELQDISC', + RTM_GETQDISC : 'RTM_GETQDISC' + } + + def __init__(self, msgtype, debug, owner_logger=None): + self.msgtype = msgtype + self.attributes = {} + self.dump_buffer = [''] + self.line_number = 1 + self.debug = debug + self.message = None + + if owner_logger: + self.log = owner_logger + else: + self.log = log + + def __str__(self): + return self.get_type_string() + + def get_string(self, to_string, index): + """ + Used to do lookups in all of the various FOO_to_string dictionaries + but returns 'UNKNOWN' if the key is bogus + """ + if index in to_string: + return to_string[index] + return 'UNKNOWN' + + def get_type_string(self, msgtype=None): + if not msgtype: + msgtype = self.msgtype + return self.get_string(self.type_to_string, msgtype) + + def get_flags_string(self): + foo = [] + + for (flag, flag_string) in self.flag_to_string.iteritems(): + if self.flags & flag: + foo.append(flag_string) + + return ', '.join(foo) + + def decode_packet(self, length, flags, seq, pid, data): + self.length = length + self.flags = flags + self.seq = seq + self.pid = pid + self.header_data = data[0:self.header_LEN] + self.msg_data = data[self.header_LEN:length] + + self.decode_netlink_header() + self.decode_service_header() + + # NLMSG_ERROR is special case, it does not have attributes to decode + if self.msgtype != NLMSG_ERROR: + self.decode_attributes() + + def get_netlink_header_flags_string(self, msg_type, flags): + foo = [] + + if flags & NLM_F_REQUEST: + foo.append('NLM_F_REQUEST') + + if flags & NLM_F_MULTI: + foo.append('NLM_F_MULTI') + + if flags & NLM_F_ACK: + foo.append('NLM_F_ACK') + + if flags & NLM_F_ECHO: + foo.append('NLM_F_ECHO') + + # Modifiers to GET query + if msg_type in (RTM_GETLINK, RTM_GETADDR, RTM_GETNEIGH, RTM_GETROUTE, RTM_GETQDISC): + if flags & NLM_F_ROOT: + foo.append('NLM_F_ROOT') + + if flags & NLM_F_MATCH: + foo.append('NLM_F_MATCH') + + if flags & NLM_F_DUMP: + foo.append('NLM_F_DUMP') + + if flags & NLM_F_ATOMIC: + foo.append('NLM_F_ATOMIC') + + # Modifiers to NEW query + elif msg_type in (RTM_NEWLINK, RTM_NEWADDR, RTM_NEWNEIGH, RTM_NEWROUTE, RTM_NEWQDISC): + if flags & NLM_F_REPLACE: + foo.append('NLM_F_REPLACE') + + if flags & NLM_F_EXCL: + foo.append('NLM_F_EXCL') + + if flags & NLM_F_CREATE: + foo.append('NLM_F_CREATE') + + if flags & NLM_F_APPEND: + foo.append('NLM_F_APPEND') + + return ', '.join(foo) + + # When we first RXed the netlink message we had to decode the header to + # determine what type of netlink message we were dealing with. So the + # header has actually already been decoded...what we do here is + # populate the dump_buffer lines with the header content. + def decode_netlink_header(self): + + if not self.debug: + return + + header_data = self.header_data + + # Print the netlink header in red + color = red + netlink_header_length = 16 + self.dump_buffer.append(" \033[%dmNetlink Header\033[0m" % color) + + for x in range(0, netlink_header_length/4): + start = x * 4 + end = start + 4 + + if self.line_number == 1: + data = unpack('=L', header_data[start:end])[0] + extra = "Length %s (%d)" % (zfilled_hex(data, 8), data) + + elif self.line_number == 2: + (data1, data2) = unpack('HH', header_data[start:end]) + extra = "Type %s (%d - %s), Flags %s (%s)" % \ + (zfilled_hex(data1, 4), data1, self.get_type_string(data1), + zfilled_hex(data2, 4), self.get_netlink_header_flags_string(data1, data2)) + + elif self.line_number == 3: + data = unpack('=L', header_data[start:end])[0] + extra = "Sequence Number %s (%d)" % (zfilled_hex(data, 8), data) + + elif self.line_number == 4: + data = unpack('=L', header_data[start:end])[0] + extra = "Process ID %s (%d)" % (zfilled_hex(data, 8), data) + else: + extra = "Unexpected line number %d" % self.line_number + + self.dump_buffer.append(data_to_color_text(self.line_number, color, header_data[start:end], extra)) + self.line_number += 1 + + def decode_attributes(self): + """ + Decode the attributes and populate the dump_buffer + """ + + if self.debug: + self.dump_buffer.append(" Attributes") + color = green + + data = self.msg_data[self.LEN:] + + while data: + (length, attr_type) = unpack('=HH', data[:4]) + + # If this is zero we will stay in this loop for forever + if not length: + self.log.error('Length is zero') + return + + if len(data) < length: + self.log.error("Buffer underrun %d < %d" % (len(data), length)) + return + + attr = self.add_attribute(attr_type, None) + + # Find the end of 'data' for this attribute and decode our section + # of 'data'. attributes are padded for alignment thus the attr_end. + # + # How the attribute is decoded/unpacked is specific per AttributeXXXX class. + attr_end = padded_length(length) + attr.decode(self, data[0:attr_end]) + + if self.debug: + self.line_number = attr.dump_lines(self.dump_buffer, self.line_number, color) + + # Alternate back and forth between green and blue + if color == green: + color = blue + else: + color = green + + data = data[attr_end:] + + def add_attribute(self, attr_type, value): + nested = True if attr_type & NLA_F_NESTED else False + net_byteorder = True if attr_type & NLA_F_NET_BYTEORDER else False + attr_type = attr_type & NLA_TYPE_MASK + + # Given an attr_type (say RTA_DST) find the type of AttributeXXXX class + # that we will use to store this attribute...AttributeIPAddress in the + # case of RTA_DST. + if attr_type in self.attribute_to_class: + (attr_string, attr_class) = self.attribute_to_class[attr_type] + else: + attr_string = "UNKNOWN_ATTRIBUTE_%d" % attr_type + attr_class = AttributeGeneric + self.log.debug("Attribute %d is not defined in %s.attribute_to_class, assuming AttributeGeneric" % + (attr_type, self.__class__.__name__)) + + # A few attribute classes must know self.family (family was extracted from + # the service header) + if attr_class == AttributeIPAddress or attr_class == AttributeRTA_MULTIPATH: + attr = attr_class(attr_type, attr_string, self.family, self.log) + else: + attr = attr_class(attr_type, attr_string, self.log) + + attr.value = value + attr.nested = nested + attr.net_byteorder = net_byteorder + + # self.attributes is a dictionary keyed by the attribute type where + # the value is an instance of the corresponding AttributeXXXX class. + self.attributes[attr_type] = attr + + return attr + + def get_attribute_value(self, attr_type): + if attr_type not in self.attributes: + return None + + return self.attributes[attr_type].value + + def get_attr_string(self, attr_type): + """ + Example: If attr_type is Address.IFA_CACHEINFO return the string 'IFA_CACHEINFO' + """ + if attr_type in self.attribute_to_class: + (attr_string, attr_class) = self.attribute_to_class[attr_type] + return attr_string + return str(attr_type) + + def build_message(self, seq, pid): + self.seq = seq + self.pid = pid + attrs = '' + + for attr in self.attributes.itervalues(): + attrs += attr.encode() + + self.length = self.header_LEN + len(self.body) + len(attrs) + self.header_data = pack(self.header_PACK, self.length, self.msgtype, self.flags, self.seq, self.pid) + self.msg_data = self.body + attrs + self.message = self.header_data + self.msg_data + + if self.debug: + self.decode_netlink_header() + self.decode_service_header() + self.decode_attributes() + self.dump("TXed %s, length %d, seq %d, pid %d, flags 0x%x (%s)" % + (self, self.length, self.seq, self.pid, self.flags, + self.get_netlink_header_flags_string(self.msgtype, self.flags))) + + # Print the netlink message in hex. This is only used for debugging. + def dump(self, desc=None): + attr_string = {} + + if desc is None: + desc = "RXed %s, length %d, seq %d, pid %d, flags 0x%x" % (self, self.length, self.seq, self.pid, self.flags) + + for (attr_type, attr_obj) in self.attributes.iteritems(): + key_string = "(%2d) %s" % (attr_type, self.get_attr_string(attr_type)) + attr_string[key_string] = attr_obj.get_pretty_value() + + self.log.debug("%s\n%s\n\nAttributes Summary\n%s\n" % + (desc, '\n'.join(self.dump_buffer), pformat(attr_string))) + + +class Address(NetlinkPacket): + """ + Service Header + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Family | Length | Flags | Scope | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Interface Index | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + """ + + # Address attributes + # /usr/include/linux/if_addr.h + IFA_UNSPEC = 0x00 + IFA_ADDRESS = 0x01 + IFA_LOCAL = 0x02 + IFA_LABEL = 0x03 + IFA_BROADCAST = 0x04 + IFA_ANYCAST = 0x05 + IFA_CACHEINFO = 0x06 + IFA_MULTICAST = 0x07 + IFA_FLAGS = 0x08 + + attribute_to_class = { + IFA_UNSPEC : ('IFA_UNSPEC', AttributeGeneric), + IFA_ADDRESS : ('IFA_ADDRESS', AttributeIPAddress), + IFA_LOCAL : ('IFA_LOCAL', AttributeIPAddress), + IFA_LABEL : ('IFA_LABEL', AttributeString), + IFA_BROADCAST : ('IFA_BROADCAST', AttributeIPAddress), + IFA_ANYCAST : ('IFA_ANYCAST', AttributeIPAddress), + IFA_CACHEINFO : ('IFA_CACHEINFO', AttributeGeneric), + IFA_MULTICAST : ('IFA_MULTICAST', AttributeIPAddress), + IFA_FLAGS : ('IFA_FLAGS', AttributeGeneric) + } + + # Address flags + # /usr/include/linux/if_addr.h + IFA_F_SECONDARY = 0x01 + IFA_F_NODAD = 0x02 + IFA_F_OPTIMISTIC = 0x04 + IFA_F_DADFAILED = 0x08 + IFA_F_HOMEADDRESS = 0x10 + IFA_F_DEPRECATED = 0x20 + IFA_F_TENTATIVE = 0x40 + IFA_F_PERMANENT = 0x80 + + flag_to_string = { + IFA_F_SECONDARY : 'IFA_F_SECONDARY', + IFA_F_NODAD : 'IFA_F_NODAD', + IFA_F_OPTIMISTIC : 'IFA_F_OPTIMISTIC', + IFA_F_DADFAILED : 'IFA_F_DADFAILED', + IFA_F_HOMEADDRESS : 'IFA_F_HOMEADDRESS', + IFA_F_DEPRECATED : 'IFA_F_DEPRECATED', + IFA_F_TENTATIVE : 'IFA_F_TENTATIVE', + IFA_F_PERMANENT : 'IFA_F_PERMANENT' + } + + def __init__(self, msgtype, debug=False, logger=None): + NetlinkPacket.__init__(self, msgtype, debug, logger) + self.PACK = '4Bi' + self.LEN = calcsize(self.PACK) + + def decode_service_header(self): + + # Nothing to do if the message did not contain a service header + if self.length == self.header_LEN: + return + + (self.family, self.prefixlen, self.flags, self.scope, + self.ifindex) = \ + unpack(self.PACK, self.msg_data[:self.LEN]) + + if self.debug: + color = yellow + self.dump_buffer.append(" \033[%dmService Header\033[0m" % color) + + for x in range(0, self.LEN/4): + if self.line_number == 5: + extra = "Family %s (%d), Length %s (%d), Flags %s, Scope %s (%d)" % \ + (zfilled_hex(self.family, 2), self.family, + zfilled_hex(self.prefixlen, 2), self.prefixlen, + zfilled_hex(self.flags, 2), + zfilled_hex(self.scope, 2), self.scope) + elif self.line_number == 6: + extra = "Interface Index %s (%d)" % (zfilled_hex(self.ifindex, 8), self.ifindex) + else: + extra = "Unexpected line number %d" % self.line_number + + start = x * 4 + end = start + 4 + self.dump_buffer.append(data_to_color_text(self.line_number, color, self.msg_data[start:end], extra)) + self.line_number += 1 + + +class Error(NetlinkPacket): + + # Error codes + # /include/netlink/errno.h + NLE_SUCCESS = 0x00 + NLE_FAILURE = 0x01 + NLE_INTR = 0x02 + NLE_BAD_SOCK = 0x03 + NLE_AGAIN = 0x04 + NLE_NOMEM = 0x05 + NLE_EXIST = 0x06 + NLE_INVAL = 0x07 + NLE_RANGE = 0x08 + NLE_MSGSIZE = 0x09 + NLE_OPNOTSUPP = 0x0A + NLE_AF_NOSUPPORT = 0x0B + NLE_OBJ_NOTFOUND = 0x0C + NLE_NOATTR = 0x0D + NLE_MISSING_ATTR = 0x0E + NLE_AF_MISMATCH = 0x0F + NLE_SEQ_MISMATCH = 0x10 + NLE_MSG_OVERFLOW = 0x11 + NLE_MSG_TRUNC = 0x12 + NLE_NOADDR = 0x13 + NLE_SRCRT_NOSUPPORT = 0x14 + NLE_MSG_TOOSHORT = 0x15 + NLE_MSGTYPE_NOSUPPORT = 0x16 + NLE_OBJ_MISMATCH = 0x17 + NLE_NOCACHE = 0x18 + NLE_BUSY = 0x19 + NLE_PROTO_MISMATCH = 0x1A + NLE_NOACCESS = 0x1B + NLE_PERM = 0x1C + NLE_PKTLOC_FILE = 0x1D + NLE_PARSE_ERR = 0x1E + NLE_NODEV = 0x1F + NLE_IMMUTABLE = 0x20 + NLE_DUMP_INTR = 0x21 + + error_to_string = { + NLE_SUCCESS : 'NLE_SUCCESS', + NLE_FAILURE : 'NLE_FAILURE', + NLE_INTR : 'NLE_INTR', + NLE_BAD_SOCK : 'NLE_BAD_SOCK', + NLE_AGAIN : 'NLE_AGAIN', + NLE_NOMEM : 'NLE_NOMEM', + NLE_EXIST : 'NLE_EXIST', + NLE_INVAL : 'NLE_INVAL', + NLE_RANGE : 'NLE_RANGE', + NLE_MSGSIZE : 'NLE_MSGSIZE', + NLE_OPNOTSUPP : 'NLE_OPNOTSUPP', + NLE_AF_NOSUPPORT : 'NLE_AF_NOSUPPORT', + NLE_OBJ_NOTFOUND : 'NLE_OBJ_NOTFOUND', + NLE_NOATTR : 'NLE_NOATTR', + NLE_MISSING_ATTR : 'NLE_MISSING_ATTR', + NLE_AF_MISMATCH : 'NLE_AF_MISMATCH', + NLE_SEQ_MISMATCH : 'NLE_SEQ_MISMATCH', + NLE_MSG_OVERFLOW : 'NLE_MSG_OVERFLOW', + NLE_MSG_TRUNC : 'NLE_MSG_TRUNC', + NLE_NOADDR : 'NLE_NOADDR', + NLE_SRCRT_NOSUPPORT : 'NLE_SRCRT_NOSUPPORT', + NLE_MSG_TOOSHORT : 'NLE_MSG_TOOSHORT', + NLE_MSGTYPE_NOSUPPORT : 'NLE_MSGTYPE_NOSUPPORT', + NLE_OBJ_MISMATCH : 'NLE_OBJ_MISMATCH', + NLE_NOCACHE : 'NLE_NOCACHE', + NLE_BUSY : 'NLE_BUSY', + NLE_PROTO_MISMATCH : 'NLE_PROTO_MISMATCH', + NLE_NOACCESS : 'NLE_NOACCESS', + NLE_PERM : 'NLE_PERM', + NLE_PKTLOC_FILE : 'NLE_PKTLOC_FILE', + NLE_PARSE_ERR : 'NLE_PARSE_ERR', + NLE_NODEV : 'NLE_NODEV', + NLE_IMMUTABLE : 'NLE_IMMUTABLE', + NLE_DUMP_INTR : 'NLE_DUMP_INTR' + } + + def __init__(self, msgtype, debug=False, logger=None): + NetlinkPacket.__init__(self, msgtype, debug, logger) + self.PACK = '=iLHHLL' + self.LEN = calcsize(self.PACK) + + def decode_service_header(self): + + # Nothing to do if the message did not contain a service header + if self.length == self.header_LEN: + return + + (self.negative_errno, self.bad_msg_len, self.bad_msg_type, + self.bad_msg_flag, self.bad_msg_seq, self.bad_msg_pid) =\ + unpack(self.PACK, self.msg_data[:self.LEN]) + + if self.debug: + color = yellow + self.dump_buffer.append(" \033[%dmService Header\033[0m" % color) + + for x in range(0, self.LEN/4): + + if self.line_number == 5: + extra = "Error Number %s is %s" % (self.negative_errno, self.error_to_string.get(abs(self.negative_errno))) + # zfilled_hex(self.negative_errno, 2) + + elif self.line_number == 6: + extra = "Length %s (%d)" % (zfilled_hex(self.bad_msg_len, 8), self.bad_msg_len) + + elif self.line_number == 7: + extra = "Type %s (%d - %s), Flags %s (%s)" % \ + (zfilled_hex(self.bad_msg_type, 4), self.bad_msg_type, self.get_type_string(self.bad_msg_type), + zfilled_hex(self.bad_msg_flag, 4), self.get_netlink_header_flags_string(self.bad_msg_type, self.bad_msg_flag)) + + elif self.line_number == 8: + extra = "Sequence Number %s (%d)" % (zfilled_hex(self.bad_msg_seq, 8), self.bad_msg_seq) + + elif self.line_number == 9: + extra = "Process ID %s (%d)" % (zfilled_hex(self.bad_msg_pid, 8), self.bad_msg_pid) + + else: + extra = "Unexpected line number %d" % self.line_number + + start = x * 4 + end = start + 4 + self.dump_buffer.append(data_to_color_text(self.line_number, color, self.msg_data[start:end], extra)) + self.line_number += 1 + + +class Link(NetlinkPacket): + """ + Service Header + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Family | Reserved | Device Type | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Interface Index | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Device Flags | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Change Mask | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + """ + + # Link attributes + # /usr/include/linux/if_link.h + 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 + IFLA_PROTINFO = 12 + 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 + IFLA_VFINFO_LIST = 22 + IFLA_STATS64 = 23 + IFLA_VF_PORTS = 24 + IFLA_PORT_SELF = 25 + IFLA_AF_SPEC = 26 + IFLA_GROUP = 27 + IFLA_NET_NS_FD = 28 + IFLA_EXT_MASK = 29 + IFLA_PROMISCUITY = 30 + IFLA_NUM_TX_QUEUES = 31 + IFLA_NUM_RX_QUEUES = 32 + IFLA_CARRIER = 33 + IFLA_PHYS_PORT_ID = 34 + IFLA_CARRIER_CHANGES = 35 + IFLA_PHYS_SWITCH_ID = 36 + IFLA_LINK_NETNSID = 37 + IFLA_PHYS_PORT_NAME = 38 + IFLA_PROTO_DOWN = 39 + IFLA_LINKPROTODOWN = 200 + + attribute_to_class = { + IFLA_UNSPEC : ('IFLA_UNSPEC', AttributeGeneric), + IFLA_ADDRESS : ('IFLA_ADDRESS', AttributeMACAddress), + IFLA_BROADCAST : ('IFLA_BROADCAST', AttributeMACAddress), + IFLA_IFNAME : ('IFLA_IFNAME', AttributeString), + IFLA_MTU : ('IFLA_MTU', AttributeFourByteValue), + IFLA_LINK : ('IFLA_LINK', AttributeFourByteValue), + IFLA_QDISC : ('IFLA_QDISC', AttributeString), + IFLA_STATS : ('IFLA_STATS', AttributeGeneric), + IFLA_COST : ('IFLA_COST', AttributeGeneric), + IFLA_PRIORITY : ('IFLA_PRIORITY', AttributeGeneric), + IFLA_MASTER : ('IFLA_MASTER', AttributeFourByteValue), + IFLA_WIRELESS : ('IFLA_WIRELESS', AttributeGeneric), + IFLA_PROTINFO : ('IFLA_PROTINFO', AttributeGeneric), + IFLA_TXQLEN : ('IFLA_TXQLEN', AttributeFourByteValue), + IFLA_MAP : ('IFLA_MAP', AttributeGeneric), + IFLA_WEIGHT : ('IFLA_WEIGHT', AttributeGeneric), + IFLA_OPERSTATE : ('IFLA_OPERSTATE', AttributeFourByteValue), + IFLA_LINKMODE : ('IFLA_LINKMODE', AttributeFourByteValue), + IFLA_LINKINFO : ('IFLA_LINKINFO', AttributeIFLA_LINKINFO), + IFLA_NET_NS_PID : ('IFLA_NET_NS_PID', AttributeGeneric), + IFLA_IFALIAS : ('IFLA_IFALIAS', AttributeGeneric), + IFLA_NUM_VF : ('IFLA_NUM_VF', AttributeGeneric), + IFLA_VFINFO_LIST : ('IFLA_VFINFO_LIST', AttributeGeneric), + IFLA_STATS64 : ('IFLA_STATS64', AttributeGeneric), + IFLA_VF_PORTS : ('IFLA_VF_PORTS', AttributeGeneric), + IFLA_PORT_SELF : ('IFLA_PORT_SELF', AttributeGeneric), + IFLA_AF_SPEC : ('IFLA_AF_SPEC', AttributeIFLA_AF_SPEC), + IFLA_GROUP : ('IFLA_GROUP', AttributeFourByteValue), + IFLA_NET_NS_FD : ('IFLA_NET_NS_FD', AttributeGeneric), + IFLA_EXT_MASK : ('IFLA_EXT_MASK', AttributeGeneric), + IFLA_PROMISCUITY : ('IFLA_PROMISCUITY', AttributeGeneric), + IFLA_NUM_TX_QUEUES : ('IFLA_NUM_TX_QUEUES', AttributeGeneric), + IFLA_NUM_RX_QUEUES : ('IFLA_NUM_RX_QUEUES', AttributeGeneric), + IFLA_CARRIER : ('IFLA_CARRIER', AttributeGeneric), + IFLA_PHYS_PORT_ID : ('IFLA_PHYS_PORT_ID', AttributeGeneric), + IFLA_CARRIER_CHANGES : ('IFLA_CARRIER_CHANGES', AttributeGeneric), + IFLA_PHYS_SWITCH_ID : ('IFLA_PHYS_SWITCH_ID', AttributeGeneric), + IFLA_LINK_NETNSID : ('IFLA_LINK_NETNSID', AttributeGeneric), + IFLA_PHYS_PORT_NAME : ('IFLA_PHYS_PORT_NAME', AttributeGeneric), + IFLA_PROTO_DOWN : ('IFLA_PROTO_DOWN', AttributeGeneric), + IFLA_LINKPROTODOWN : ('IFLA_LINKPROTODOWN', AttributeGeneric) + } + + # Link flags + # /usr/include/linux/if.h + IFF_UP = 0x0001 # Interface is administratively up. + IFF_BROADCAST = 0x0002 # Valid broadcast address set. + IFF_DEBUG = 0x0004 # Internal debugging flag. + IFF_LOOPBACK = 0x0008 # Interface is a loopback interface. + IFF_POINTOPOINT = 0x0010 # Interface is a point-to-point link. + IFF_NOTRAILERS = 0x0020 # Avoid use of trailers. + IFF_RUNNING = 0x0040 # Interface is operationally up. + IFF_NOARP = 0x0080 # No ARP protocol needed for this interface. + IFF_PROMISC = 0x0100 # Interface is in promiscuous mode. + IFF_ALLMULTI = 0x0200 # Receive all multicast packets. + IFF_MASTER = 0x0400 # Master of a load balancing bundle. + IFF_SLAVE = 0x0800 # Slave of a load balancing bundle. + IFF_MULTICAST = 0x1000 # Supports multicast. + IFF_PORTSEL = 0x2000 # Is able to select media type via ifmap. + IFF_AUTOMEDIA = 0x4000 # Auto media selection active. + IFF_DYNAMIC = 0x8000 # Interface was dynamically created. + IFF_LOWER_UP = 0x10000 # driver signals L1 up + IFF_DORMANT = 0x20000 # driver signals dormant + IFF_ECHO = 0x40000 # echo sent packet + IFF_PROTO_DOWN = 0x1000000 # protocol is down on the interface + + flag_to_string = { + IFF_UP : 'IFF_UP', + IFF_BROADCAST : 'IFF_BROADCAST', + IFF_DEBUG : 'IFF_DEBUG', + IFF_LOOPBACK : 'IFF_LOOPBACK', + IFF_POINTOPOINT : 'IFF_POINTOPOINT', + IFF_NOTRAILERS : 'IFF_NOTRAILERS', + IFF_RUNNING : 'IFF_RUNNING', + IFF_NOARP : 'IFF_NOARP', + IFF_PROMISC : 'IFF_PROMISC', + IFF_ALLMULTI : 'IFF_ALLMULTI', + IFF_MASTER : 'IFF_MASTER', + IFF_SLAVE : 'IFF_SLAVE', + IFF_MULTICAST : 'IFF_MULTICAST', + IFF_PORTSEL : 'IFF_PORTSEL', + IFF_AUTOMEDIA : 'IFF_AUTOMEDIA', + IFF_DYNAMIC : 'IFF_DYNAMIC', + IFF_LOWER_UP : 'IFF_LOWER_UP', + IFF_DORMANT : 'IFF_DORMANT', + IFF_ECHO : 'IFF_ECHO', + IFF_PROTO_DOWN : 'IFF_PROTO_DOWN' + } + + # RFC 2863 operational status + IF_OPER_UNKNOWN = 0 + IF_OPER_NOTPRESENT = 1 + IF_OPER_DOWN = 2 + IF_OPER_LOWERLAYERDOWN = 3 + IF_OPER_TESTING = 4 + IF_OPER_DORMANT = 5 + IF_OPER_UP = 6 + + oper_to_string = { + IF_OPER_UNKNOWN : 'IF_OPER_UNKNOWN', + IF_OPER_NOTPRESENT : 'IF_OPER_NOTPRESENT', + IF_OPER_DOWN : 'IF_OPER_DOWN', + IF_OPER_LOWERLAYERDOWN : 'IF_OPER_LOWERLAYERDOWN', + IF_OPER_TESTING : 'IF_OPER_TESTING', + IF_OPER_DORMANT : 'IF_OPER_DORMANT', + IF_OPER_UP : 'IF_OPER_UP' + } + + # Link types + # /usr/include/linux/if_arp.h + # ARP protocol HARDWARE identifiers + ARPHRD_NETROM = 0 # from KA9Q: NET/ROM pseudo + ARPHRD_ETHER = 1 # Ethernet 10Mbps + ARPHRD_EETHER = 2 # Experimental Ethernet + ARPHRD_AX25 = 3 # AX.25 Level 2 + ARPHRD_PRONET = 4 # PROnet token ring + ARPHRD_CHAOS = 5 # Chaosnet + ARPHRD_IEEE802 = 6 # IEEE 802.2 Ethernet/TR/TB + ARPHRD_ARCNET = 7 # ARCnet + ARPHRD_APPLETLK = 8 # APPLEtalk + ARPHRD_DLCI = 15 # Frame Relay DLCI + ARPHRD_ATM = 19 # ATM + ARPHRD_METRICOM = 23 # Metricom STRIP (new IANA id) + ARPHRD_IEEE1394 = 24 # IEEE 1394 IPv4 - RFC 2734 + ARPHRD_EUI64 = 27 # EUI-64 + ARPHRD_INFINIBAND = 32 # InfiniBand + # Dummy types for non ARP hardware + ARPHRD_SLIP = 256 + ARPHRD_CSLIP = 257 + ARPHRD_SLIP6 = 258 + ARPHRD_CSLIP6 = 259 + ARPHRD_RSRVD = 260 # Notional KISS type + ARPHRD_ADAPT = 264 + ARPHRD_ROSE = 270 + ARPHRD_X25 = 271 # CCITT X.25 + ARPHRD_HWX25 = 272 # Boards with X.25 in firmware + ARPHRD_CAN = 280 # Controller Area Network + ARPHRD_PPP = 512 + ARPHRD_CISCO = 513 # Cisco HDLC + ARPHRD_HDLC = ARPHRD_CISCO + ARPHRD_LAPB = 516 # LAPB + ARPHRD_DDCMP = 517 # Digital's DDCMP protocol + ARPHRD_RAWHDLC = 518 # Raw HDLC + ARPHRD_TUNNEL = 768 # IPIP tunnel + ARPHRD_TUNNEL6 = 769 # IP6IP6 tunnel + ARPHRD_FRAD = 770 # Frame Relay Access Device + ARPHRD_SKIP = 771 # SKIP vif + ARPHRD_LOOPBACK = 772 # Loopback device + ARPHRD_LOCALTLK = 773 # Localtalk device + ARPHRD_FDDI = 774 # Fiber Distributed Data Interface + ARPHRD_BIF = 775 # AP1000 BIF + ARPHRD_SIT = 776 # sit0 device - IPv6-in-IPv4 + ARPHRD_IPDDP = 777 # IP over DDP tunneller + ARPHRD_IPGRE = 778 # GRE over IP + ARPHRD_PIMREG = 779 # PIMSM register interface + ARPHRD_HIPPI = 780 # High Performance Parallel Interface + ARPHRD_ASH = 781 # Nexus 64Mbps Ash + ARPHRD_ECONET = 782 # Acorn Econet + ARPHRD_IRDA = 783 # Linux-IrDA + ARPHRD_FCPP = 784 # Point to point fibrechannel + ARPHRD_FCAL = 785 # Fibrechannel arbitrated loop + ARPHRD_FCPL = 786 # Fibrechannel public loop + ARPHRD_FCFABRIC = 787 # Fibrechannel fabric + # 787->799 reserved for fibrechannel media types + ARPHRD_IEEE802_TR = 800 # Magic type ident for TR + ARPHRD_IEEE80211 = 801 # IEEE 802.11 + ARPHRD_IEEE80211_PRISM = 802 # IEEE 802.11 + Prism2 header + ARPHRD_IEEE80211_RADIOTAP = 803 # IEEE 802.11 + radiotap header + ARPHRD_IEEE802154 = 804 + ARPHRD_PHONET = 820 # PhoNet media type + ARPHRD_PHONET_PIPE = 821 # PhoNet pipe header + ARPHRD_CAIF = 822 # CAIF media type + ARPHRD_VOID = 0xFFFF # Void type, nothing is known + ARPHRD_NONE = 0xFFFE # zero header length + + link_type_to_string = { + ARPHRD_NETROM : 'ARPHRD_NETROM', + ARPHRD_ETHER : 'ARPHRD_ETHER', + ARPHRD_EETHER : 'ARPHRD_EETHER', + ARPHRD_AX25 : 'ARPHRD_AX25', + ARPHRD_PRONET : 'ARPHRD_PRONET', + ARPHRD_CHAOS : 'ARPHRD_CHAOS', + ARPHRD_IEEE802 : 'ARPHRD_IEEE802', + ARPHRD_ARCNET : 'ARPHRD_ARCNET', + ARPHRD_APPLETLK : 'ARPHRD_APPLETLK', + ARPHRD_DLCI : 'ARPHRD_DLCI', + ARPHRD_ATM : 'ARPHRD_ATM', + ARPHRD_METRICOM : 'ARPHRD_METRICOM', + ARPHRD_IEEE1394 : 'ARPHRD_IEEE1394', + ARPHRD_EUI64 : 'ARPHRD_EUI64', + ARPHRD_INFINIBAND : 'ARPHRD_INFINIBAND', + ARPHRD_SLIP : 'ARPHRD_SLIP', + ARPHRD_CSLIP : 'ARPHRD_CSLIP', + ARPHRD_SLIP6 : 'ARPHRD_SLIP6', + ARPHRD_CSLIP6 : 'ARPHRD_CSLIP6', + ARPHRD_RSRVD : 'ARPHRD_RSRVD', + ARPHRD_ADAPT : 'ARPHRD_ADAPT', + ARPHRD_ROSE : 'ARPHRD_ROSE', + ARPHRD_X25 : 'ARPHRD_X25', + ARPHRD_HWX25 : 'ARPHRD_HWX25', + ARPHRD_CAN : 'ARPHRD_CAN', + ARPHRD_PPP : 'ARPHRD_PPP', + ARPHRD_CISCO : 'ARPHRD_CISCO', + ARPHRD_HDLC : 'ARPHRD_HDLC', + ARPHRD_LAPB : 'ARPHRD_LAPB', + ARPHRD_DDCMP : 'ARPHRD_DDCMP', + ARPHRD_RAWHDLC : 'ARPHRD_RAWHDLC', + ARPHRD_TUNNEL : 'ARPHRD_TUNNEL', + ARPHRD_TUNNEL6 : 'ARPHRD_TUNNEL6', + ARPHRD_FRAD : 'ARPHRD_FRAD', + ARPHRD_SKIP : 'ARPHRD_SKIP', + ARPHRD_LOOPBACK : 'ARPHRD_LOOPBACK', + ARPHRD_LOCALTLK : 'ARPHRD_LOCALTLK', + ARPHRD_FDDI : 'ARPHRD_FDDI', + ARPHRD_BIF : 'ARPHRD_BIF', + ARPHRD_SIT : 'ARPHRD_SIT', + ARPHRD_IPDDP : 'ARPHRD_IPDDP', + ARPHRD_IPGRE : 'ARPHRD_IPGRE', + ARPHRD_PIMREG : 'ARPHRD_PIMREG', + ARPHRD_HIPPI : 'ARPHRD_HIPPI', + ARPHRD_ASH : 'ARPHRD_ASH', + ARPHRD_ECONET : 'ARPHRD_ECONET', + ARPHRD_IRDA : 'ARPHRD_IRDA', + ARPHRD_FCPP : 'ARPHRD_FCPP', + ARPHRD_FCAL : 'ARPHRD_FCAL', + ARPHRD_FCPL : 'ARPHRD_FCPL', + ARPHRD_FCFABRIC : 'ARPHRD_FCFABRIC', + ARPHRD_IEEE802_TR : 'ARPHRD_IEEE802_TR', + ARPHRD_IEEE80211 : 'ARPHRD_IEEE80211', + ARPHRD_IEEE80211_PRISM : 'ARPHRD_IEEE80211_PRISM', + ARPHRD_IEEE80211_RADIOTAP : 'ARPHRD_IEEE80211_RADIOTAP', + ARPHRD_IEEE802154 : 'ARPHRD_IEEE802154', + ARPHRD_PHONET : 'ARPHRD_PHONET', + ARPHRD_PHONET_PIPE : 'ARPHRD_PHONET_PIPE', + ARPHRD_CAIF : 'ARPHRD_CAIF', + ARPHRD_VOID : 'ARPHRD_VOID', + ARPHRD_NONE : 'ARPHRD_NONE' + } + + # ========================================= + # IFLA_LINKINFO attributes + # ========================================= + IFLA_INFO_UNSPEC = 0 + IFLA_INFO_KIND = 1 + IFLA_INFO_DATA = 2 + IFLA_INFO_XSTATS = 3 + IFLA_INFO_SLAVE_KIND = 4 + IFLA_INFO_SLAVE_DATA = 5 + IFLA_INFO_MAX = 6 + + ifla_info_to_string = { + IFLA_INFO_UNSPEC : 'IFLA_INFO_UNSPEC', + IFLA_INFO_KIND : 'IFLA_INFO_KIND', + IFLA_INFO_DATA : 'IFLA_INFO_DATA', + IFLA_INFO_XSTATS : 'IFLA_INFO_XSTATS', + IFLA_INFO_SLAVE_KIND : 'IFLA_INFO_SLAVE_KIND', + IFLA_INFO_SLAVE_DATA : 'IFLA_INFO_SLAVE_DATA', + IFLA_INFO_MAX : 'IFLA_INFO_MAX' + } + + # ========================================= + # IFLA_INFO_DATA attributes for vlan + # ========================================= + IFLA_VLAN_UNSPEC = 0 + IFLA_VLAN_ID = 1 + IFLA_VLAN_FLAGS = 2 + IFLA_VLAN_EGRESS_QOS = 3 + IFLA_VLAN_INGRESS_QOS = 4 + IFLA_VLAN_PROTOCOL = 5 + + ifla_vlan_to_string = { + IFLA_VLAN_UNSPEC : 'IFLA_VLAN_UNSPEC', + IFLA_VLAN_ID : 'IFLA_VLAN_ID', + IFLA_VLAN_FLAGS : 'IFLA_VLAN_FLAGS', + IFLA_VLAN_EGRESS_QOS : 'IFLA_VLAN_EGRESS_QOS', + IFLA_VLAN_INGRESS_QOS : 'IFLA_VLAN_INGRESS_QOS', + IFLA_VLAN_PROTOCOL : 'IFLA_VLAN_PROTOCOL' + } + + # ========================================= + # IFLA_INFO_DATA attributes for macvlan + # ========================================= + IFLA_MACVLAN_UNSPEC = 0 + IFLA_MACVLAN_MODE = 1 + + ifla_macvlan_to_string = { + IFLA_MACVLAN_UNSPEC : 'IFLA_MACVLAN_UNSPEC', + IFLA_MACVLAN_MODE : 'IFLA_MACVLAN_MODE' + } + + # macvlan modes + MACVLAN_MODE_PRIVATE = 1 + MACVLAN_MODE_VEPA = 2 + MACVLAN_MODE_BRIDGE = 3 + MACVLAN_MODE_PASSTHRU = 4 + + macvlan_mode_to_string = { + MACVLAN_MODE_PRIVATE : 'MACVLAN_MODE_PRIVATE', + MACVLAN_MODE_VEPA : 'MACVLAN_MODE_VEPA', + MACVLAN_MODE_BRIDGE : 'MACVLAN_MODE_BRIDGE', + MACVLAN_MODE_PASSTHRU : 'MACVLAN_MODE_PASSTHRU' + } + + # ========================================= + # IFLA_INFO_DATA attributes for vxlan + # ========================================= + IFLA_VXLAN_UNSPEC = 0 + IFLA_VXLAN_ID = 1 + IFLA_VXLAN_GROUP = 2 + IFLA_VXLAN_LINK = 3 + IFLA_VXLAN_LOCAL = 4 + IFLA_VXLAN_TTL = 5 + IFLA_VXLAN_TOS = 6 + IFLA_VXLAN_LEARNING = 7 + IFLA_VXLAN_AGEING = 8 + IFLA_VXLAN_LIMIT = 9 + IFLA_VXLAN_PORT_RANGE = 10 + IFLA_VXLAN_PROXY = 11 + IFLA_VXLAN_RSC = 12 + IFLA_VXLAN_L2MISS = 13 + IFLA_VXLAN_L3MISS = 14 + IFLA_VXLAN_PORT = 15 + IFLA_VXLAN_GROUP6 = 16 + IFLA_VXLAN_LOCAL6 = 17 + IFLA_VXLAN_UDP_CSUM = 18 + IFLA_VXLAN_UDP_ZERO_CSUM6_TX = 19 + IFLA_VXLAN_UDP_ZERO_CSUM6_RX = 20 + IFLA_VXLAN_REMCSUM_TX = 21 + IFLA_VXLAN_REMCSUM_RX = 22 + IFLA_VXLAN_GBP = 23 + IFLA_VXLAN_REMCSUM_NOPARTIAL = 24 + IFLA_VXLAN_COLLECT_METADATA = 25 + IFLA_VXLAN_REPLICATION_NODE = 253 + IFLA_VXLAN_REPLICATION_TYPE = 254 + + ifla_vxlan_to_string = { + IFLA_VXLAN_UNSPEC : 'IFLA_VXLAN_UNSPEC', + IFLA_VXLAN_ID : 'IFLA_VXLAN_ID', + IFLA_VXLAN_GROUP : 'IFLA_VXLAN_GROUP', + IFLA_VXLAN_LINK : 'IFLA_VXLAN_LINK', + IFLA_VXLAN_LOCAL : 'IFLA_VXLAN_LOCAL', + IFLA_VXLAN_TTL : 'IFLA_VXLAN_TTL', + IFLA_VXLAN_TOS : 'IFLA_VXLAN_TOS', + IFLA_VXLAN_LEARNING : 'IFLA_VXLAN_LEARNING', + IFLA_VXLAN_AGEING : 'IFLA_VXLAN_AGEING', + IFLA_VXLAN_LIMIT : 'IFLA_VXLAN_LIMIT', + IFLA_VXLAN_PORT_RANGE : 'IFLA_VXLAN_PORT_RANGE', + IFLA_VXLAN_PROXY : 'IFLA_VXLAN_PROXY', + IFLA_VXLAN_RSC : 'IFLA_VXLAN_RSC', + IFLA_VXLAN_L2MISS : 'IFLA_VXLAN_L2MISS', + IFLA_VXLAN_L3MISS : 'IFLA_VXLAN_L3MISS', + IFLA_VXLAN_PORT : 'IFLA_VXLAN_PORT', + IFLA_VXLAN_GROUP6 : 'IFLA_VXLAN_GROUP6', + IFLA_VXLAN_LOCAL6 : 'IFLA_VXLAN_LOCAL6', + IFLA_VXLAN_UDP_CSUM : 'IFLA_VXLAN_UDP_CSUM', + IFLA_VXLAN_UDP_ZERO_CSUM6_TX : 'IFLA_VXLAN_UDP_ZERO_CSUM6_TX', + IFLA_VXLAN_UDP_ZERO_CSUM6_RX : 'IFLA_VXLAN_UDP_ZERO_CSUM6_RX', + IFLA_VXLAN_REMCSUM_TX : 'IFLA_VXLAN_REMCSUM_TX', + IFLA_VXLAN_REMCSUM_RX : 'IFLA_VXLAN_REMCSUM_RX', + IFLA_VXLAN_GBP : 'IFLA_VXLAN_GBP', + IFLA_VXLAN_REMCSUM_NOPARTIAL : 'IFLA_VXLAN_REMCSUM_NOPARTIAL', + IFLA_VXLAN_COLLECT_METADATA : 'IFLA_VXLAN_COLLECT_METADATA', + IFLA_VXLAN_REPLICATION_NODE : 'IFLA_VXLAN_REPLICATION_NODE', + IFLA_VXLAN_REPLICATION_TYPE : 'IFLA_VXLAN_REPLICATION_TYPE' + } + + # ========================================= + # IFLA_INFO_DATA attributes for bonds + # ========================================= + IFLA_BOND_UNSPEC = 0 + IFLA_BOND_MODE = 1 + IFLA_BOND_ACTIVE_SLAVE = 2 + IFLA_BOND_MIIMON = 3 + IFLA_BOND_UPDELAY = 4 + IFLA_BOND_DOWNDELAY = 5 + IFLA_BOND_USE_CARRIER = 6 + IFLA_BOND_ARP_INTERVAL = 7 + IFLA_BOND_ARP_IP_TARGET = 8 + IFLA_BOND_ARP_VALIDATE = 9 + IFLA_BOND_ARP_ALL_TARGETS = 10 + IFLA_BOND_PRIMARY = 11 + IFLA_BOND_PRIMARY_RESELECT = 12 + IFLA_BOND_FAIL_OVER_MAC = 13 + IFLA_BOND_XMIT_HASH_POLICY = 14 + IFLA_BOND_RESEND_IGMP = 15 + IFLA_BOND_NUM_PEER_NOTIF = 16 + IFLA_BOND_ALL_SLAVES_ACTIVE = 17 + IFLA_BOND_MIN_LINKS = 18 + IFLA_BOND_LP_INTERVAL = 19 + IFLA_BOND_PACKETS_PER_SLAVE = 20 + IFLA_BOND_AD_LACP_RATE = 21 + IFLA_BOND_AD_SELECT = 22 + IFLA_BOND_AD_INFO = 23 + IFLA_BOND_AD_ACTOR_SYS_PRIO = 24 + IFLA_BOND_AD_USER_PORT_KEY = 25 + IFLA_BOND_AD_ACTOR_SYSTEM = 26 + IFLA_BOND_CL_LACP_BYPASS_ALLOW = 100 + IFLA_BOND_CL_LACP_BYPASS_ACTIVE = 101 + IFLA_BOND_CL_LACP_BYPASS_PERIOD = 102 + IFLA_BOND_CL_CLAG_ENABLE = 103 + IFLA_BOND_CL_LACP_BYPASS_ALL_ACTIVE = 104 + + ifla_bond_to_string = { + IFLA_BOND_UNSPEC : 'IFLA_BOND_UNSPEC', + IFLA_BOND_MODE : 'IFLA_BOND_MODE', + IFLA_BOND_ACTIVE_SLAVE : 'IFLA_BOND_ACTIVE_SLAVE', + IFLA_BOND_MIIMON : 'IFLA_BOND_MIIMON', + IFLA_BOND_UPDELAY : 'IFLA_BOND_UPDELAY', + IFLA_BOND_DOWNDELAY : 'IFLA_BOND_DOWNDELAY', + IFLA_BOND_USE_CARRIER : 'IFLA_BOND_USE_CARRIER', + IFLA_BOND_ARP_INTERVAL : 'IFLA_BOND_ARP_INTERVAL', + IFLA_BOND_ARP_IP_TARGET : 'IFLA_BOND_ARP_IP_TARGET', + IFLA_BOND_ARP_VALIDATE : 'IFLA_BOND_ARP_VALIDATE', + IFLA_BOND_ARP_ALL_TARGETS : 'IFLA_BOND_ARP_ALL_TARGETS', + IFLA_BOND_PRIMARY : 'IFLA_BOND_PRIMARY', + IFLA_BOND_PRIMARY_RESELECT : 'IFLA_BOND_PRIMARY_RESELECT', + IFLA_BOND_FAIL_OVER_MAC : 'IFLA_BOND_FAIL_OVER_MAC', + IFLA_BOND_XMIT_HASH_POLICY : 'IFLA_BOND_XMIT_HASH_POLICY', + IFLA_BOND_RESEND_IGMP : 'IFLA_BOND_RESEND_IGMP', + IFLA_BOND_NUM_PEER_NOTIF : 'IFLA_BOND_NUM_PEER_NOTIF', + IFLA_BOND_ALL_SLAVES_ACTIVE : 'IFLA_BOND_ALL_SLAVES_ACTIVE', + IFLA_BOND_MIN_LINKS : 'IFLA_BOND_MIN_LINKS', + IFLA_BOND_LP_INTERVAL : 'IFLA_BOND_LP_INTERVAL', + IFLA_BOND_PACKETS_PER_SLAVE : 'IFLA_BOND_PACKETS_PER_SLAVE', + IFLA_BOND_AD_LACP_RATE : 'IFLA_BOND_AD_LACP_RATE', + IFLA_BOND_AD_SELECT : 'IFLA_BOND_AD_SELECT', + IFLA_BOND_AD_INFO : 'IFLA_BOND_AD_INFO', + IFLA_BOND_AD_ACTOR_SYS_PRIO : 'IFLA_BOND_AD_ACTOR_SYS_PRIO', + IFLA_BOND_AD_USER_PORT_KEY : 'IFLA_BOND_AD_USER_PORT_KEY', + IFLA_BOND_AD_ACTOR_SYSTEM : 'IFLA_BOND_AD_ACTOR_SYSTEM', + IFLA_BOND_CL_LACP_BYPASS_ALLOW : 'IFLA_BOND_CL_LACP_BYPASS_ALLOW', + IFLA_BOND_CL_LACP_BYPASS_ACTIVE : 'IFLA_BOND_CL_LACP_BYPASS_ACTIVE', + IFLA_BOND_CL_LACP_BYPASS_PERIOD : 'IFLA_BOND_CL_LACP_BYPASS_PERIOD', + IFLA_BOND_CL_CLAG_ENABLE : 'IFLA_BOND_CL_CLAG_ENABLE', + IFLA_BOND_CL_LACP_BYPASS_ALL_ACTIVE : 'IFLA_BOND_CL_LACP_BYPASS_ALL_ACTIVE' + } + + # ========================================= + # IFLA_INFO_DATA attributes for bridges + # ========================================= + IFLA_BRPORT_UNSPEC = 0 + IFLA_BRPORT_STATE = 1 + IFLA_BRPORT_PRIORITY = 2 + IFLA_BRPORT_COST = 3 + IFLA_BRPORT_MODE = 4 + IFLA_BRPORT_GUARD = 5 + IFLA_BRPORT_PROTECT = 6 + IFLA_BRPORT_FAST_LEAVE = 7 + IFLA_BRPORT_LEARNING = 8 + IFLA_BRPORT_UNICAST_FLOOD = 9 + IFLA_BRPORT_PROXYARP = 10 + IFLA_BRPORT_LEARNING_SYNC = 11 + IFLA_BRPORT_PROXYARP_WIFI = 12 + IFLA_BRPORT_ROOT_ID = 13 + IFLA_BRPORT_BRIDGE_ID = 14 + IFLA_BRPORT_DESIGNATED_PORT = 15 + IFLA_BRPORT_DESIGNATED_COST = 16 + IFLA_BRPORT_ID = 17 + IFLA_BRPORT_NO = 18 + IFLA_BRPORT_TOPOLOGY_CHANGE_ACK = 19 + IFLA_BRPORT_CONFIG_PENDING = 20 + IFLA_BRPORT_MESSAGE_AGE_TIMER = 21 + IFLA_BRPORT_FORWARD_DELAY_TIMER = 22 + IFLA_BRPORT_HOLD_TIMER = 23 + IFLA_BRPORT_FLUSH = 24 + IFLA_BRPORT_MULTICAST_ROUTER = 25 + IFLA_BRPORT_PEER_LINK = 150 + IFLA_BRPORT_DUAL_LINK = 151 + + ifla_bridge_to_string = { + IFLA_BRPORT_UNSPEC : 'IFLA_BRPORT_UNSPEC', + IFLA_BRPORT_STATE : 'IFLA_BRPORT_STATE', + IFLA_BRPORT_PRIORITY : 'IFLA_BRPORT_PRIORITY', + IFLA_BRPORT_COST : 'IFLA_BRPORT_COST', + IFLA_BRPORT_MODE : 'IFLA_BRPORT_MODE', + IFLA_BRPORT_GUARD : 'IFLA_BRPORT_GUARD', + IFLA_BRPORT_PROTECT : 'IFLA_BRPORT_PROTECT', + IFLA_BRPORT_FAST_LEAVE : 'IFLA_BRPORT_FAST_LEAVE', + IFLA_BRPORT_LEARNING : 'IFLA_BRPORT_LEARNING', + IFLA_BRPORT_UNICAST_FLOOD : 'IFLA_BRPORT_UNICAST_FLOOD', + IFLA_BRPORT_PROXYARP : 'IFLA_BRPORT_PROXYARP', + IFLA_BRPORT_LEARNING_SYNC : 'IFLA_BRPORT_LEARNING_SYNC', + IFLA_BRPORT_PROXYARP_WIFI : 'IFLA_BRPORT_PROXYARP_WIFI', + IFLA_BRPORT_ROOT_ID : 'IFLA_BRPORT_ROOT_ID', + IFLA_BRPORT_BRIDGE_ID : 'IFLA_BRPORT_BRIDGE_ID', + IFLA_BRPORT_DESIGNATED_PORT : 'IFLA_BRPORT_DESIGNATED_PORT', + IFLA_BRPORT_DESIGNATED_COST : 'IFLA_BRPORT_DESIGNATED_COST', + IFLA_BRPORT_ID : 'IFLA_BRPORT_ID', + IFLA_BRPORT_NO : 'IFLA_BRPORT_NO', + IFLA_BRPORT_TOPOLOGY_CHANGE_ACK : 'IFLA_BRPORT_TOPOLOGY_CHANGE_ACK', + IFLA_BRPORT_CONFIG_PENDING : 'IFLA_BRPORT_CONFIG_PENDING', + IFLA_BRPORT_MESSAGE_AGE_TIMER : 'IFLA_BRPORT_MESSAGE_AGE_TIMER', + IFLA_BRPORT_FORWARD_DELAY_TIMER : 'IFLA_BRPORT_FORWARD_DELAY_TIMER', + IFLA_BRPORT_HOLD_TIMER : 'IFLA_BRPORT_HOLD_TIMER', + IFLA_BRPORT_FLUSH : 'IFLA_BRPORT_FLUSH', + IFLA_BRPORT_MULTICAST_ROUTER : 'IFLA_BRPORT_MULTICAST_ROUTER', + IFLA_BRPORT_PEER_LINK : 'IFLA_BRPORT_PEER_LINK', + IFLA_BRPORT_DUAL_LINK : 'IFLA_BRPORT_DUAL_LINK' + } + + # BRIDGE IFLA_AF_SPEC attributes + IFLA_BRIDGE_FLAGS = 0 + IFLA_BRIDGE_MODE = 1 + IFLA_BRIDGE_VLAN_INFO = 2 + + ifla_bridge_af_spec_to_string = { + IFLA_BRIDGE_FLAGS : 'IFLA_BRIDGE_FLAGS', + IFLA_BRIDGE_MODE : 'IFLA_BRIDGE_MODE', + IFLA_BRIDGE_VLAN_INFO : 'IFLA_BRIDGE_VLAN_INFO' + } + + # BRIDGE_VLAN_INFO flags + BRIDGE_VLAN_INFO_MASTER = 1 + BRIDGE_VLAN_INFO_PVID = 2 + BRIDGE_VLAN_INFO_UNTAGGED = 4 + + bridge_vlan_to_string = { + BRIDGE_VLAN_INFO_MASTER : 'BRIDGE_VLAN_INFO_MASTER', + BRIDGE_VLAN_INFO_PVID : 'BRIDGE_VLAN_INFO_PVID', + BRIDGE_VLAN_INFO_UNTAGGED : 'BRIDGE_VLAN_INFO_UNTAGGED' + } + + # Bridge flags + BRIDGE_FLAGS_MASTER = 1 + BRIDGE_FLAGS_SELF = 2 + + bridge_flags_to_string = { + BRIDGE_FLAGS_MASTER : 'BRIDGE_FLAGS_MASTER', + BRIDGE_FLAGS_SELF : 'BRIDGE_FLAGS_SELF' + } + + def __init__(self, msgtype, debug=False, logger=None): + NetlinkPacket.__init__(self, msgtype, debug, logger) + self.PACK = 'BxHiII' + self.LEN = calcsize(self.PACK) + + def get_link_type_string(self, index): + return self.get_string(self.link_type_to_string, index) + + def get_ifla_bridge_af_spec_to_string(self, index): + return self.get_string(self.ifla_bridge_af_spec_to_string, index) + + def get_ifla_info_string(self, index): + return self.get_string(self.ifla_info_to_string, index) + + def get_ifla_vlan_string(self, index): + return self.get_string(self.ifla_vlan_to_string, index) + + def get_ifla_vxlan_string(self, index): + return self.get_string(self.ifla_vxlan_to_string, index) + + def get_ifla_macvlan_string(self, index): + return self.get_string(self.ifla_macvlan_to_string, index) + + def get_macvlan_mode_string(self, index): + return self.get_string(self.macvlan_mode_to_string, index) + + def get_ifla_bond_string(self, index): + return self.get_string(self.ifla_bond_to_string, index) + + def get_ifla_bridge_string(self, index): + return self.get_string(self.ifla_bridge_to_string, index) + + def get_bridge_vlan_string(self, index): + return self.get_string(self.bridge_vlan_to_string, index) + + def get_bridge_flags_string(self, index): + return self.get_string(self.bridge_flags_to_string, index) + + def decode_service_header(self): + + # Nothing to do if the message did not contain a service header + if self.length == self.header_LEN: + return + + (self.family, self.device_type, + self.ifindex, + self.flags, + self.change_mask) = \ + unpack(self.PACK, self.msg_data[:self.LEN]) + + if self.debug: + color = yellow + self.dump_buffer.append(" \033[%dmService Header\033[0m" % color) + for x in range(0, self.LEN/4): + if self.line_number == 5: + extra = "Family %s (%d), Device Type %s (%d - %s)" % \ + (zfilled_hex(self.family, 2), self.family, + zfilled_hex(self.device_type, 4), self.device_type, self.get_link_type_string(self.device_type)) + elif self.line_number == 6: + extra = "Interface Index %s (%d)" % (zfilled_hex(self.ifindex, 8), self.ifindex) + elif self.line_number == 7: + extra = "Device Flags %s (%s)" % (zfilled_hex(self.flags, 8), self.get_flags_string()) + elif self.line_number == 8: + extra = "Change Mask %s" % zfilled_hex(self.change_mask, 8) + else: + extra = "Unexpected line number %d" % self.line_number + + start = x * 4 + end = start + 4 + self.dump_buffer.append(data_to_color_text(self.line_number, color, self.msg_data[start:end], extra)) + self.line_number += 1 + + def is_up(self): + if self.flags & Link.IFF_UP: + return True + return False + + +class Neighbor(NetlinkPacket): + """ + Service Header + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Family | Reserved1 | Reserved2 | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Interface Index | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | State | Flags | Type | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + """ + + # Neighbor attributes + # /usr/include/linux/neighbour.h + NDA_UNSPEC = 0x00 # Unknown type + NDA_DST = 0x01 # A neighbour cache network. layer destination address + NDA_LLADDR = 0x02 # A neighbor cache link layer address. + NDA_CACHEINFO = 0x03 # Cache statistics + NDA_PROBES = 0x04 + NDA_VLAN = 0x05 + NDA_PORT = 0x06 + NDA_VNI = 0x07 + NDA_IFINDEX = 0x08 + NDA_MASTER = 0x09 + NDA_LINK_NETNSID = 0x0A + + attribute_to_class = { + NDA_UNSPEC : ('NDA_UNSPEC', AttributeGeneric), + NDA_DST : ('NDA_DST', AttributeIPAddress), + NDA_LLADDR : ('NDA_LLADDR', AttributeMACAddress), + NDA_CACHEINFO : ('NDA_CACHEINFO', AttributeGeneric), + NDA_PROBES : ('NDA_PROBES', AttributeFourByteValue), + NDA_VLAN : ('NDA_VLAN', AttributeGeneric), + NDA_PORT : ('NDA_PORT', AttributeGeneric), + NDA_VNI : ('NDA_VNI', AttributeGeneric), + NDA_IFINDEX : ('NDA_IFINDEX', AttributeGeneric), + NDA_MASTER : ('NDA_MASTER', AttributeGeneric), + NDA_LINK_NETNSID : ('NDA_LINK_NETNSID', AttributeGeneric) + } + + # Neighbor flags + # /usr/include/linux/neighbour.h + NTF_USE = 0x01 + NTF_PROXY = 0x08 # A proxy ARP entry + NTF_ROUTER = 0x80 # An IPv6 router + + flag_to_string = { + NTF_USE : 'NTF_USE', + NTF_PROXY : 'NTF_PROXY', + NTF_ROUTER : 'NTF_ROUTER' + } + + # Neighbor states + # /usr/include/linux/neighbour.h + NUD_NONE = 0x00 + NUD_INCOMPLETE = 0x01 # Still attempting to resolve + NUD_REACHABLE = 0x02 # A confirmed working cache entry + NUD_STALE = 0x04 # an expired cache entry + NUD_DELAY = 0x08 # Neighbor no longer reachable. Traffic sent, waiting for confirmatio. + NUD_PROBE = 0x10 # A cache entry that is currently being re-solicited + NUD_FAILED = 0x20 # An invalid cache entry + NUD_NOARP = 0x40 # A device which does not do neighbor discovery(ARP) + NUD_PERMANENT = 0x80 # A static entry + + state_to_string = { + NUD_NONE : 'NUD_NONE', + NUD_INCOMPLETE : 'NUD_INCOMPLETE', + NUD_REACHABLE : 'NUD_REACHABLE', + NUD_STALE : 'NUD_STALE', + NUD_DELAY : 'NUD_DELAY', + NUD_PROBE : 'NUD_PROBE', + NUD_FAILED : 'NUD_FAILED', + NUD_NOARP : 'NUD_NOARP', + NUD_PERMANENT : 'NUD_PERMANENT' + } + + def __init__(self, msgtype, debug=False, logger=None): + NetlinkPacket.__init__(self, msgtype, debug, logger) + self.PACK = 'BxxxiHBB' + self.LEN = calcsize(self.PACK) + + def get_state_string(self, index): + return self.get_string(self.state_to_string, index) + + def decode_service_header(self): + + # Nothing to do if the message did not contain a service header + if self.length == self.header_LEN: + return + + (self.family, + self.ifindex, + self.state, self.flags, self.neighbor_type) = \ + unpack(self.PACK, self.msg_data[:self.LEN]) + + if self.debug: + color = yellow + self.dump_buffer.append(" \033[%dmService Header\033[0m" % color) + + for x in range(0, self.LEN/4): + if self.line_number == 5: + extra = "Family %s (%d)" % (zfilled_hex(self.family, 2), self.family) + elif self.line_number == 6: + extra = "Interface Index %s (%d)" % (zfilled_hex(self.ifindex, 8), self.ifindex) + elif self.line_number == 7: + extra = "State %s (%d), Flags %s, Type %s (%d)" % \ + (zfilled_hex(self.state, 4), self.state, + zfilled_hex(self.flags, 2), + zfilled_hex(self.neighbor_type, 4), self.neighbor_type) + else: + extra = "Unexpected line number %d" % self.line_number + + start = x * 4 + end = start + 4 + self.dump_buffer.append(data_to_color_text(self.line_number, color, self.msg_data[start:end], extra)) + self.line_number += 1 + + +class Route(NetlinkPacket): + """ + Service Header + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Family | Dest length | Src length | TOS | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Table ID | Protocol | Scope | Type | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Flags | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + """ + + # Route attributes + # /usr/include/linux/rtnetlink.h + RTA_UNSPEC = 0x00 # Ignored. + RTA_DST = 0x01 # Protocol address for route destination address. + RTA_SRC = 0x02 # Protocol address for route source address. + RTA_IIF = 0x03 # Input interface index. + RTA_OIF = 0x04 # Output interface index. + RTA_GATEWAY = 0x05 # Protocol address for the gateway of the route + RTA_PRIORITY = 0x06 # Priority of broker. + RTA_PREFSRC = 0x07 # Preferred source address in cases where more than one source address could be used. + RTA_METRICS = 0x08 # Route metrics attributed to route and associated protocols(e.g., RTT, initial TCP window, etc.). + RTA_MULTIPATH = 0x09 # Multipath route next hop's attributes. + RTA_PROTOINFO = 0x0A # Firewall based policy routing attribute. + RTA_FLOW = 0x0B # Route realm. + RTA_CACHEINFO = 0x0C # Cached route information. + RTA_SESSION = 0x0D + RTA_MP_ALGO = 0x0E + RTA_TABLE = 0x0F + RTA_MARK = 0x10 + + attribute_to_class = { + RTA_UNSPEC : ('RTA_UNSPEC', AttributeGeneric), + RTA_DST : ('RTA_DST', AttributeIPAddress), + RTA_SRC : ('RTA_SRC', AttributeIPAddress), + RTA_IIF : ('RTA_IIF', AttributeFourByteValue), + RTA_OIF : ('RTA_OIF', AttributeFourByteValue), + RTA_GATEWAY : ('RTA_GATEWAY', AttributeIPAddress), + RTA_PRIORITY : ('RTA_PRIORITY', AttributeFourByteValue), + RTA_PREFSRC : ('RTA_PREFSRC', AttributeIPAddress), + RTA_METRICS : ('RTA_METRICS', AttributeGeneric), + RTA_MULTIPATH : ('RTA_MULTIPATH', AttributeRTA_MULTIPATH), + RTA_PROTOINFO : ('RTA_PROTOINFO', AttributeGeneric), + RTA_FLOW : ('RTA_FLOW', AttributeGeneric), + RTA_CACHEINFO : ('RTA_CACHEINFO', AttributeGeneric), + RTA_SESSION : ('RTA_SESSION', AttributeGeneric), + RTA_MP_ALGO : ('RTA_MP_ALGO', AttributeGeneric), + RTA_TABLE : ('RTA_TABLE', AttributeFourByteValue), + RTA_MARK : ('RTA_MARK', AttributeGeneric) + } + + # Route tables + # /usr/include/linux/rtnetlink.h + RT_TABLE_UNSPEC = 0x00 # An unspecified routing table + RT_TABLE_COMPAT = 0xFC + RT_TABLE_DEFAULT = 0xFD # The default table + RT_TABLE_MAIN = 0xFE # The main table + RT_TABLE_LOCAL = 0xFF # The local table + + table_to_string = { + RT_TABLE_UNSPEC : 'RT_TABLE_UNSPEC', + RT_TABLE_COMPAT : 'RT_TABLE_COMPAT', + RT_TABLE_DEFAULT : 'RT_TABLE_DEFAULT', + RT_TABLE_MAIN : 'RT_TABLE_MAIN', + RT_TABLE_LOCAL : 'RT_TABLE_LOCAL' + } + + # Route scope + # /usr/include/linux/rtnetlink.h + RT_SCOPE_UNIVERSE = 0x00 # Global route + RT_SCOPE_SITE = 0xC8 # Interior route in the local autonomous system + RT_SCOPE_LINK = 0xFD # Route on this link + RT_SCOPE_HOST = 0xFE # Route on the local host + RT_SCOPE_NOWHERE = 0xFF # Destination does not exist + + scope_to_string = { + RT_SCOPE_UNIVERSE : 'RT_SCOPE_UNIVERSE', + RT_SCOPE_SITE : 'RT_SCOPE_SITE', + RT_SCOPE_LINK : 'RT_SCOPE_LINK', + RT_SCOPE_HOST : 'RT_SCOPE_HOST', + RT_SCOPE_NOWHERE : 'RT_SCOPE_NOWHERE' + } + + # Routing stack + # /usr/include/linux/rtnetlink.h + RT_PROT_UNSPEC = 0x00 # Identifies what/who added the route + RT_PROT_REDIRECT = 0x01 # By an ICMP redirect + RT_PROT_KERNEL = 0x02 # By the kernel + RT_PROT_BOOT = 0x03 # During bootup + RT_PROT_STATIC = 0x04 # By the administrator + RT_PROT_GATED = 0x08 # GateD + RT_PROT_RA = 0x09 # RDISC/ND router advertissements + RT_PROT_MRT = 0x0A # Merit MRT + RT_PROT_ZEBRA = 0x0B # ZEBRA + RT_PROT_BIRD = 0x0C # BIRD + RT_PROT_DNROUTED = 0x0D # DECnet routing daemon + RT_PROT_XORP = 0x0E # XORP + RT_PROT_NTK = 0x0F # Netsukuku + RT_PROT_DHCP = 0x10 # DHCP client + RT_PROT_EXABGP = 0x11 # Exa Networks ExaBGP + + prot_to_string = { + RT_PROT_UNSPEC : 'RT_PROT_UNSPEC', + RT_PROT_REDIRECT : 'RT_PROT_REDIRECT', + RT_PROT_KERNEL : 'RT_PROT_KERNEL', + RT_PROT_BOOT : 'RT_PROT_BOOT', + RT_PROT_STATIC : 'RT_PROT_STATIC', + RT_PROT_GATED : 'RT_PROT_GATED', + RT_PROT_RA : 'RT_PROT_RA', + RT_PROT_MRT : 'RT_PROT_MRT', + RT_PROT_ZEBRA : 'RT_PROT_ZEBRA', + RT_PROT_BIRD : 'RT_PROT_BIRD', + RT_PROT_DNROUTED : 'RT_PROT_DNROUTED', + RT_PROT_XORP : 'RT_PROT_XORP', + RT_PROT_NTK : 'RT_PROT_NTK', + RT_PROT_DHCP : 'RT_PROT_DHCP', + RT_PROT_EXABGP : 'RT_PROT_EXABGP' + } + + # Route types + # /usr/include/linux/rtnetlink.h + RTN_UNSPEC = 0x00 # Unknown broker. + RTN_UNICAST = 0x01 # A gateway or direct broker. + RTN_LOCAL = 0x02 # A local interface broker. + RTN_BROADCAST = 0x03 # A local broadcast route(sent as a broadcast). + RTN_ANYCAST = 0x04 # An anycast broker. + RTN_MULTICAST = 0x05 # A multicast broker. + RTN_BLACKHOLE = 0x06 # A silent packet dropping broker. + RTN_UNREACHABLE = 0x07 # An unreachable destination. Packets dropped and + # host unreachable ICMPs are sent to the originator. + RTN_PROHIBIT = 0x08 # A packet rejection broker. Packets are dropped and + # communication prohibited ICMPs are sent to the originator. + RTN_THROW = 0x09 # When used with policy routing, continue routing lookup + # in another table. Under normal routing, packets are + # dropped and net unreachable ICMPs are sent to the originator. + RTN_NAT = 0x0A # A network address translation rule. + RTN_XRESOLVE = 0x0B # Refer to an external resolver(not implemented). + + rt_type_to_string = { + RTN_UNSPEC : 'RTN_UNSPEC', + RTN_UNICAST : 'RTN_UNICAST', + RTN_LOCAL : 'RTN_LOCAL', + RTN_BROADCAST : 'RTN_BROADCAST', + RTN_ANYCAST : 'RTN_ANYCAST', + RTN_MULTICAST : 'RTN_MULTICAST', + RTN_BLACKHOLE : 'RTN_BLACKHOLE', + RTN_UNREACHABLE : 'RTN_UNREACHABLE', + RTN_PROHIBIT : 'RTN_PROHIBIT', + RTN_THROW : 'RTN_THROW', + RTN_NAT : 'RTN_NAT', + RTN_XRESOLVE : 'RTN_XRESOLVE' + } + + # Route flags + # /usr/include/linux/rtnetlink.h + RTM_F_NOTIFY = 0x100 # If the route changes, notify the user + RTM_F_CLONED = 0x200 # Route is cloned from another route + RTM_F_EQUALIZE = 0x400 # Allow randomization of next hop path in multi-path routing(currently not implemented) + RTM_F_PREFIX = 0x800 # Prefix Address + + flag_to_string = { + RTM_F_NOTIFY : 'RTM_F_NOTIFY', + RTM_F_CLONED : 'RTM_F_CLONED', + RTM_F_EQUALIZE : 'RTM_F_EQUALIZE', + RTM_F_PREFIX : 'RTM_F_PREFIX' + } + + def __init__(self, msgtype, debug=False, logger=None): + NetlinkPacket.__init__(self, msgtype, debug, logger) + self.PACK = '=8BI' # or is it 8Bi ? + self.LEN = calcsize(self.PACK) + self.family = None + + def get_prefix_string(self): + dst = self.get_attribute_value(self.RTA_DST) + + if dst: + return "%s/%d" % (dst, self.src_len) + else: + if self.family == AF_INET: + return "0.0.0.0/0" + elif self.family == AF_INET6: + return "::/0" + + def get_protocol_string(self, index=None): + if index is None: + index = self.protocol + return self.get_string(self.prot_to_string, index) + + def get_rt_type_string(self, index=None): + if index is None: + index = self.route_type + return self.get_string(self.rt_type_to_string, index) + + def get_scope_string(self, index=None): + if index is None: + index = self.scope + return self.get_string(self.scope_to_string, index) + + def get_table_id_string(self, index=None): + if index is None: + index = self.table_id + return self.get_string(self.table_to_string, index) + + def _get_ifname_from_index(self, ifindex, ifname_by_index): + if ifindex: + ifname = ifname_by_index.get(ifindex) + + if ifname is None: + ifname = str(ifindex) + else: + ifname = None + + return ifname + + def get_nexthops(self, ifname_by_index={}): + nexthop = self.get_attribute_value(self.RTA_GATEWAY) + multipath = self.get_attribute_value(self.RTA_MULTIPATH) + nexthops = [] + + if nexthop: + rta_oif = self.get_attribute_value(self.RTA_OIF) + ifname = self._get_ifname_from_index(rta_oif, ifname_by_index) + nexthops.append((nexthop, ifname)) + + elif multipath: + for (nexthop, rtnh_ifindex, rtnh_flags, rtnh_hops) in multipath: + ifname = self._get_ifname_from_index(rtnh_ifindex, ifname_by_index) + nexthops.append((nexthop, ifname)) + + return nexthops + + def get_nexthops_string(self, ifname_by_index={}): + output = [] + + for (nexthop, ifname) in self.get_nexthops(ifname_by_index): + output.append(" via %s on %s" % (nexthop, ifname)) + + return ",".join(output) + + def decode_service_header(self): + + # Nothing to do if the message did not contain a service header + if self.length == self.header_LEN: + return + + (self.family, self.src_len, self.dst_len, self.tos, + self.table_id, self.protocol, self.scope, self.route_type, + self.flags) = \ + unpack(self.PACK, self.msg_data[:self.LEN]) + + if self.debug: + color = yellow + self.dump_buffer.append(" \033[%dmService Header\033[0m" % color) + + for x in range(0, self.LEN/4): + if self.line_number == 5: + extra = "Family %s (%d), Source Length %s (%d), Destination Length %s (%d), TOS %s (%d)" % \ + (zfilled_hex(self.family, 2), self.family, + zfilled_hex(self.src_len, 2), self.src_len, + zfilled_hex(self.dst_len, 2), self.dst_len, + zfilled_hex(self.tos, 2), self.tos) + elif self.line_number == 6: + extra = "Table ID %s (%d - %s), Protocol %s (%d - %s), Scope %s (%d - %s), Type %s (%d - %s)" % \ + (zfilled_hex(self.table_id, 2), self.table_id, self.get_table_id_string(), + zfilled_hex(self.protocol, 2), self.protocol, self.get_protocol_string(), + zfilled_hex(self.scope, 2), self.scope, self.get_scope_string(), + zfilled_hex(self.route_type, 2), self.route_type, self.get_rt_type_string()) + elif self.line_number == 7: + extra = "Flags %s" % zfilled_hex(self.flags, 8) + else: + extra = "Unexpected line number %d" % self.line_number + + start = x * 4 + end = start + 4 + self.dump_buffer.append(data_to_color_text(self.line_number, color, self.msg_data[start:end], extra)) + self.line_number += 1 diff --git a/packages/ifupdown2/ifupdown/ifupdownconfig.py b/packages/ifupdown2/ifupdown/ifupdownconfig.py new file mode 100644 index 0000000..b156b6a --- /dev/null +++ b/packages/ifupdown2/ifupdown/ifupdownconfig.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python +# +# Copyright 2015 Cumulus Networks, Inc. All rights reserved. +# +# Author: Roopa Prabhu, roopa@cumulusnetworks.com +# +# + +class ifupdownConfig(): + + def __init__(self): + self.conf = {} + +config = ifupdownConfig() diff --git a/sbin/ifaddon b/sbin/ifaddon new file mode 100755 index 0000000..193331e --- /dev/null +++ b/sbin/ifaddon @@ -0,0 +1,351 @@ +#!/usr/bin/python + +import sys +import os +import re +import argparse +from collections import OrderedDict + +lockfile="/run/network/.lock" +modules_configfile='/var/lib/ifupdownaddons/addons.conf' +modules_dir='/usr/share/ifupdownaddons' + +addon_config = OrderedDict([('pre-up', []), + ('up', []), + ('post-up', []), + ('pre-down', []), + ('down', []), + ('post-down', [])]) + +def read_modules_config(): + with open(modules_configfile, 'r') as f: + lines = f.readlines() + for l in lines: + litems = l.rstrip(' \n').split(',') + operation = litems[0] + mname = litems[1] + addon_config[operation].append(mname) + +def man_rst_header(): + print '==========================' + print 'ifupdown-addons-interfaces' + print '==========================' + + print '---------------------------------------------------------' + print 'ifupdown2 addon modules interface configuration' + print '---------------------------------------------------------' + + print ':Author: roopa@cumulusnetworks.com' + print ':Date: 2013-09-25' + print ':Copyright: Copyright 2013 Cumulus Networks, Inc. All rights reserved.' + print ':Version: 0.1' + print ':Manual section: 5' + print '\n' + +def man_rst_body(): + + print 'DESCRIPTION' + print '===========' + + print (''' 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. ''') + + print '\n' + +def get_addon_modinfo(modules_dir): + """ load python modules from modules_dir + + Default modules_dir is /usr/share/ifupdownmodules + + """ + if not modules_dir in sys.path: + sys.path.append(modules_dir) + read_modules_config() + modinfo = {} + try: + for op, mlist in addon_config.items(): + for mname in mlist: + if mname in modinfo.keys(): continue + mpath = modules_dir + '/' + mname + '.py' + if os.path.exists(mpath): + try: + m = __import__(mname) + mclass = getattr(m, mname) + except: + pass + continue + minstance = mclass() + if hasattr(minstance, 'get_modinfo'): + modinfo[mname] = minstance.get_modinfo() + except: + raise + + return modinfo + +def print_long_string(indent, strarg): + slen = 70 - len(indent) + tmphelpstr = strarg + l = len(strarg) + while l > 0: + rem = slen if l >= slen else l + print('%s%s' %(indent, tmphelpstr[:rem])) + tmphelpstr = tmphelpstr[rem:].strip() + l -= rem + +def man_rst_examples(): + print 'EXAMPLES' + print '========' + print ''' Listed below are addon modules and their supported attributes. + The attributes if applicable go under the iface section in the + interfaces(5) file.\n''' + + indent = ' ' + modinfo = get_addon_modinfo(modules_dir) + for m, mdict in modinfo.items(): + aindent = indent + ' ' + aindentplus = aindent + ' ' + if not mdict: + continue + print_long_string(indent, '**%s**: %s' %(m, mdict.get('mhelp', ''))) + attrdict = mdict.get('attrs') + if not attrdict: + continue + print '\n' + try: + for attrname, attrvaldict in attrdict.items(): + if attrvaldict.get('compat', False): + continue + print('%s**%s**\n' %(aindent, attrname)) + print_long_string(aindentplus, '**help**: %s' + %(attrvaldict.get('help', ''))) + print '\n' + print('%s**required**: %s\n' %(aindentplus, + attrvaldict.get('required', False))) + default = attrvaldict.get('default') + if default: + print('%s**default**: %s\n' %(aindentplus, default)) + validrange = attrvaldict.get('validrange') + if validrange: + print('%svalidrange: %s\n' + %(aindentplus, '-'.join(validrange))) + validvals = attrvaldict.get('validvals') + if validvals: + print('%s**validvals**: %s\n' + %(aindentplus, ','.join(validvals))) + examples = attrvaldict.get('example') + if not examples: + continue + print '%s**example**:' %(aindentplus) + for e in examples: + print '%s%s\n' %(aindentplus + indent, e) + print '' + except Exception, e: + print "Roopa: m = %s, str(e) = %s\n" %(m, str(e)) + pass + print '' + +def man_rst_see_also(): + print 'SEE ALSO' + print '========' + print ''' interfaces(5), + ifup(8), + ip(8), + mstpctl(8), + brctl(8), + ethtool(8), + clagctl(8)''' + +def show_man_rst(): + man_rst_header() + man_rst_body() + man_rst_examples() + man_rst_see_also() + +def show(): + for operation, mlist in addon_config.items(): + postion = 1 + for m in mlist: + print '%d. %s' %(postion, m) + postion += 1 + +def write_modules_config(): + with open(modules_configfile, 'w') as f: + for op, mlist in addon_config.items(): + [f.write('%s,%s\n' %(op, m)) for m in mlist] + +def process_add_cmd(args): + op = args.operation + module = args.module + position = args.position + if not op: + for k, vlist in addon_config.items(): + if module not in vlist: + addon_config[k].append(module) + else: + print '%s: module %s already present' %(k, module) + return + if module in addon_config.get(op): + print 'module already present' + return + if position: + try: + addon_config[op].insert(position, module) + except Exception, e: + print ('error inserting module %s at postion %s (%s)' + %(module, position, str(e))) + raise + else: + addon_config[op].append(module) + + +def process_del_cmd(args): + op = args.operation + module = args.module + + if op: + del addon_config[op] + else: + try: + [addon_config[op].remove(module) for op in addon_config.keys()] + except ValueError: + pass + +def process_move_cmd(args): + op = args.operation + module = args.module + pos = 0 + + try: + pos = int(args.position) + if pos < 0 or pos > len(addon_config.get(op)): + raise Exception('invalid value for position') + except: + raise + + if addon_config[op].index(module) == pos: + print '%s module %s already at location %d' %(op, module, pos) + return + + addon_config[op].remove(module) + addon_config[op].insert(pos, module) + +def print_mlist(mlist, indent): + for idx, val in enumerate(mlist): + print '%s%d. %s' %(indent, idx, val) + +def process_show_cmd(args): + indent = ' ' + op = args.operation + + if args.man: + show_man_rst() + return + + if op: + mlist = addon_config[op] + print '%s:' %op + print_mlist(mlist, indent) + else: + for op, mlist in addon_config.items(): + print '%s:' %op + print_mlist(mlist, indent) + print '' + +cmdhandlers = {'add' : process_add_cmd, + 'del' : process_del_cmd, + 'move' : process_move_cmd, + 'show' : process_show_cmd} + +def update_subparser_add(subparser): + subparser.add_argument('module', metavar='MODULE', help='module name') + subparser.add_argument('operation', metavar='OPERATION', + choices=['pre-up', 'up', 'post-up', + 'pre-down', 'down', 'post-down'], + help='operations', nargs='?') + subparser.add_argument('position', metavar='POSITION', nargs='?', + help='position') + subparser.set_defaults(func=process_add_cmd) + +def update_subparser_del(subparser): + subparser.add_argument('module', metavar='MODULE', help='module name') + subparser.add_argument('operation', metavar='OPERATION', + choices=['pre-up', 'up', 'post-up', + 'pre-down', 'down', 'post-down'], + help='operations', nargs='?') + subparser.add_argument('position', metavar='POSITION', nargs='?', + help='position') + subparser.set_defaults(func=process_del_cmd) + +def update_subparser_move(subparser): + subparser.add_argument('module', metavar='MODULE', help='module name') + subparser.add_argument('operation', metavar='OPERATION', + choices=['pre-up', 'up', 'post-up', + 'pre-down', 'down', 'post-down'], + help='operations') + subparser.add_argument('position', metavar='POSITION', + help='position') + subparser.set_defaults(func=process_move_cmd) + + +def update_subparser_show(subparser): + subparser.add_argument('--man', action='store_true', + help=argparse.SUPPRESS) + subparser.add_argument('operation', metavar='OPERATION', + choices=addon_config.keys(), + help='operations %s' %str(addon_config.keys()), + nargs='?') + subparser.set_defaults(func=process_show_cmd) + +def update_argparser(argparser): + subparsers = argparser.add_subparsers(help='sub-command help') + + parser_add = subparsers.add_parser('add') + update_subparser_add(parser_add) + + parser_del = subparsers.add_parser('del', help='del help') + update_subparser_del(parser_del) + + parser_move = subparsers.add_parser('move', help='move help') + update_subparser_move(parser_move) + + parser_show = subparsers.add_parser('show', help='show help') + update_subparser_show(parser_show) + +def parse_args(argsv): + descr = 'ifupdown addon modules management command.\n \ + This command helps add/del/display/reorder modules \n \ + in all ifupdown module categories' + + argparser = argparse.ArgumentParser(description=descr) + update_argparser(argparser) + + args = argparser.parse_args(argsv) + return args + +def main(argv): + """ main function """ + try: + # Command line arg parser + args = parse_args(argv[1:]) + read_modules_config() + args.func(args) + write_modules_config() + except Exception, e: + print 'error processing command (%s)' %str(e) + +if __name__ == "__main__": + if not os.geteuid() == 0: + print 'Error: Must be root to run this command' + exit(1) + + main(sys.argv) diff --git a/sbin/ifupdown2 b/sbin/ifupdown2 new file mode 100755 index 0000000..3a56ade --- /dev/null +++ b/sbin/ifupdown2 @@ -0,0 +1,542 @@ +#!/usr/bin/python +# +# Copyright 2014 Cumulus Networks, Inc. All rights reserved. +# Author: Roopa Prabhu, roopa@cumulusnetworks.com +# +# ifupdown -- +# tool to configure network interfaces +# +import sys +import os +import argcomplete +import argparse +import ConfigParser +import StringIO +import logging +import logging.handlers +import resource +import pkg_resources +from ifupdown.ifupdownmain import * +from ifupdown.utils import * + +lockfile="/run/network/.lock" +configfile="/etc/network/ifupdown2/ifupdown2.conf" +configmap_g=None +logger = None +interfacesfileiobuf=None +interfacesfilename=None +ENVPATH = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + +def run_up(args): + logger.debug('args = %s' %str(args)) + + try: + iflist = args.iflist + if len(args.iflist) == 0: + iflist = None + logger.debug('creating ifupdown object ..') + cachearg=(False if (iflist or args.nocache or + args.perfmode or args.noact) + else True) + ifupdown_handle = ifupdownMain(config=configmap_g, + force=args.force, + withdepends=args.withdepends, + perfmode=args.perfmode, + dryrun=args.noact, + cache=cachearg, + addons_enable=not args.noaddons, + statemanager_enable=not args.noaddons, + interfacesfile=interfacesfilename, + interfacesfileiobuf=interfacesfileiobuf, + interfacesfileformat=args.interfacesfileformat) + if args.noaddons: + ifupdown_handle.up(['up'], args.all, args.CLASS, iflist, + excludepats=args.excludepats, + printdependency=args.printdependency, + 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, type=args.type, + skipupperifaces=args.skipupperifaces) + except: + raise + +def run_down(args): + logger.debug('args = %s' %str(args)) + + try: + iflist = args.iflist + logger.debug('creating ifupdown object ..') + ifupdown_handle = ifupdownMain(config=configmap_g, force=args.force, + withdepends=args.withdepends, + perfmode=args.perfmode, + dryrun=args.noact, + addons_enable=not args.noaddons, + statemanager_enable=not args.noaddons, + interfacesfile=interfacesfilename, + interfacesfileiobuf=interfacesfileiobuf, + interfacesfileformat=args.interfacesfileformat) + + ifupdown_handle.down(['pre-down', 'down', 'post-down'], + args.all, args.CLASS, iflist, + excludepats=args.excludepats, + printdependency=args.printdependency, + usecurrentconfig=args.usecurrentconfig, + type=args.type) + except: + raise + +def run_query(args): + logger.debug('args = %s' %str(args)) + + try: + iflist = args.iflist + if args.checkcurr: + qop='query-checkcurr' + elif args.running: + qop='query-running' + elif args.raw: + qop='query-raw' + elif args.syntaxhelp: + qop = 'query-syntax' + elif args.printdependency: + qop = 'query-dependency' + elif args.printsavedstate: + qop = 'query-savedstate' + else: + qop='query' + cachearg=(False if (iflist or args.nocache or + args.perfmode or args.syntaxhelp or + (qop != 'query-checkcurr' and + qop != 'query-running')) else True) + if not iflist and qop == 'query-running': + iflist = [i for i in os.listdir('/sys/class/net/') + if os.path.isdir('/sys/class/net/%s' %i)] + logger.debug('creating ifupdown object ..') + ifupdown_handle = ifupdownMain(config=configmap_g, + withdepends=args.withdepends, + perfmode=args.perfmode, + cache=cachearg, + interfacesfile=interfacesfilename, + interfacesfileiobuf=interfacesfileiobuf, + interfacesfileformat=args.interfacesfileformat, + withdefaults=args.withdefaults) + # list implies all auto interfaces (this is how ifupdown behaves) + if args.list: + args.all = True + ifupdown_handle.query([qop], args.all, args.list, args.CLASS, iflist, + excludepats=args.excludepats, + printdependency=args.printdependency, + format=args.format, type=args.type) + except: + raise + +def run_reload(args): + logger.debug('args = %s' %str(args)) + + try: + logger.debug('creating ifupdown object ..') + ifupdown_handle = ifupdownMain(config=configmap_g, + interfacesfile=interfacesfilename, + withdepends=args.withdepends, + perfmode=args.perfmode, + dryrun=args.noact) + ifupdown_handle.reload(['pre-up', 'up', 'post-up'], + ['pre-down', 'down', 'post-down'], + auto=args.all, allow=args.CLASS, ifacenames=None, + excludepats=args.excludepats, + usecurrentconfig=args.usecurrentconfig, + syntaxcheck=args.syntaxcheck, + currentlyup=args.currentlyup) + except: + raise + +def init(args): + global logger + global interfacesfileiobuf + global interfacesfilename + + log_level = logging.WARNING + if args.verbose: + log_level = logging.INFO + if args.debug: + log_level = logging.DEBUG + + try: + 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 + + if hasattr(args, 'interfacesfile') and args.interfacesfile != None: + # Check to see if -i option is allowed by config file + # But for ifquery, we will not check this + if (not sys.argv[0].endswith('ifquery') and + configmap_g.get('disable_cli_interfacesfile','0') == '1'): + logger.error('disable_cli_interfacesfile is set so users ' + 'not allowed to specify interfaces file on cli.') + exit(1) + if args.interfacesfile == '-': + # If interfaces file is stdin, read + interfacesfileiobuf = sys.stdin.read() + else: + interfacesfilename = args.interfacesfile + else: + # if the ifupdown2 config file does not have it, default to standard + interfacesfilename = configmap_g.get('default_interfaces_configfile', + '/etc/network/interfaces') + + + + +def deinit(): + {} + +def update_argparser(argparser): + """ base parser, common to all commands """ + + argparser.add_argument('-a', '--all', action='store_true', required=False, + help='process all interfaces marked \"auto\"') + argparser.add_argument('iflist', metavar='IFACE', + nargs='*', help='interface list separated by spaces. ' + + 'IFACE list is mutually exclusive with -a option.') + argparser.add_argument('-v', '--verbose', dest='verbose', + action='store_true', help='verbose') + argparser.add_argument('-d', '--debug', dest='debug', + action='store_true', + help='output debug info') + argparser.add_argument('-q', '--quiet', dest='quiet', + action='store_true', + help=argparse.SUPPRESS) + argparser.add_argument('--allow', dest='CLASS', + help='ignore non-\"allow-CLASS\" interfaces') + argparser.add_argument('-w', '--with-depends', dest='withdepends', + action='store_true', help='run with all dependent interfaces.'+ + ' This option is redundant when \'-a\' is specified. With ' + + '\'-a\' interfaces are always executed in dependency order') + argparser.add_argument('--perfmode', dest='perfmode', + action='store_true', help=argparse.SUPPRESS) + #argparser.add_argument('-j', '--jobs', dest='jobs', type=int, + # default=-1, choices=range(1,12), help=argparse.SUPPRESS) + argparser.add_argument('--nocache', dest='nocache', action='store_true', + help=argparse.SUPPRESS) + argparser.add_argument('-X', '--exclude', dest='excludepats', + action='append', + help='Exclude interfaces from the list of interfaces' + + ' to operate on. Can be specified multiple times.') + argparser.add_argument('-i', '--interfaces', dest='interfacesfile', + default=None, + help='Specify interfaces file instead of file defined ' + + 'in ifupdown2.conf file') + argparser.add_argument('-t', '--interfaces-format', + dest='interfacesfileformat', + 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,' + + 'but don\'t do it') + group.add_argument('-p', '--print-dependency', + dest='printdependency', choices=['list', 'dot'], + help='print iface dependency') + group.add_argument('--no-scripts', '--admin-state', + dest='noaddons', action='store_true', + help='dont run any addon modules/scripts. Only bring the ' + + 'interface administratively up/down') + +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): + update_ifupdown_argparser(argparser) + argparser.add_argument('-u', '--use-current-config', + dest='usecurrentconfig', action='store_true', + help='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') + +def update_ifquery_argparser(argparser): + """ arg parser for ifquery options """ + + # -l is same as '-a', only here for backward compatibility + argparser.add_argument('-l', '--list', action='store_true', dest='list', + help='list all matching known interfaces') + group = argparser.add_mutually_exclusive_group(required=False) + group.add_argument('-r', '--running', dest='running', + action='store_true', + help='query running state of an interface') + group.add_argument('-c', '--check', dest='checkcurr', + action='store_true', + help='check interface file contents against ' + + 'running state of an interface') + group.add_argument('-x', '--raw', action='store_true', dest='raw', + help='print raw config file entries') + group.add_argument('--print-savedstate', action='store_true', + dest='printsavedstate', + help=argparse.SUPPRESS) + argparser.add_argument('-o', '--format', dest='format', default='native', + choices=['native', 'json'], + help='interface display format') + argparser.add_argument('-p', '--print-dependency', + dest='printdependency', choices=['list', 'dot'], + help='print interface dependency') + argparser.add_argument('-s', '--syntax-help', action='store_true', + dest='syntaxhelp', + help='print supported interface config syntax') + argparser.add_argument('--with-defaults', action='store_true', + dest='withdefaults', + help='check policy default file contents, ' + + 'for unconfigured attributes, against ' + + 'running state of an interface') + +def update_ifreload_argparser(argparser): + """ parser for ifreload """ + 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') + group.add_argument('--allow', dest='CLASS', + help='ignore non-\"allow-CLASS\" interfaces') + argparser.add_argument('iflist', metavar='IFACE', + nargs='*', help=argparse.SUPPRESS) + argparser.add_argument('-n', '--no-act', dest='noact', + action='store_true', help='print out what would happen,' + + 'but don\'t do it') + argparser.add_argument('-v', '--verbose', dest='verbose', + action='store_true', help='verbose') + argparser.add_argument('-d', '--debug', dest='debug', + action='store_true', + help='output debug info') + argparser.add_argument('-w', '--with-depends', dest='withdepends', + action='store_true', help=argparse.SUPPRESS) + argparser.add_argument('--perfmode', dest='perfmode', + action='store_true', help=argparse.SUPPRESS) + argparser.add_argument('--nocache', dest='nocache', action='store_true', + help=argparse.SUPPRESS) + argparser.add_argument('-X', '--exclude', dest='excludepats', + action='append', + help=argparse.SUPPRESS) + #argparser.add_argument('-j', '--jobs', dest='jobs', type=int, + # default=-1, choices=range(1,12), help=argparse.SUPPRESS) + #argparser.add_argument('-i', '--interfaces', dest='interfacesfile', + # default='/etc/network/interfaces', + # help='use interfaces file instead of default ' + + # '/etc/network/interfaces') + argparser.add_argument('-u', '--use-current-config', + dest='usecurrentconfig', action='store_true', + help='By default ifreload looks at saved state for ' + + 'interfaces to bring down. With this option ifreload will' + ' 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') + argparser.add_argument('-s', '--syntax-check', dest='syntaxcheck', + action='store_true', + help='Only run the interfaces file parser') + +def update_common_argparser(argparser): + ''' general parsing rules ''' + + package = pkg_resources.get_distribution("ifupdown2") + argparser.add_argument('-V', '--version', + action='version', + version='ifupdown2:%(prog)s ' + package.version, + help='display current ifupdown2 version') + +def parse_args(argsv, op): + if op == 'query': + descr = 'query interfaces (all or interface list)' + elif op == 'reload': + descr = 'reload interface configuration.' + else: + descr = 'interface management' + argparser = argparse.ArgumentParser(description=descr) + if op == 'reload': + update_ifreload_argparser(argparser) + else: + update_argparser(argparser) + if op == 'up': + update_ifup_argparser(argparser) + elif op == 'down': + update_ifdown_argparser(argparser) + elif op == 'query': + update_ifquery_argparser(argparser) + update_common_argparser(argparser) + argcomplete.autocomplete(argparser) + return argparser.parse_args(argsv) + +handlers = {'up' : run_up, + 'down' : run_down, + 'query' : run_query, + 'reload' : run_reload } + +def validate_args(op, args): + #if op == 'up' and args.syntaxcheck: + # if args.iflist or args.all: + # print 'ignoring interface options ..' + # return True + if op == 'query' and (args.syntaxhelp or args.list): + return True + if op == 'reload': + if not args.all and not args.currentlyup and not args.CLASS: + print '\'-a\' or \'-c\' or \'-allow\' option is required' + return False + elif (not args.iflist and + not args.all and not args.CLASS): + print '\'-a\' option or interface list are required' + return False + if args.iflist and args.all: + print '\'-a\' option and interface list are mutually exclusive' + return False + if op != 'reload' and args.CLASS and (args.all or args.iflist): + print ('\'--allow\' option is mutually exclusive ' + + 'with interface list and \'-a\'') + return False + return True + +def read_config(args): + global configmap_g + + with open(configfile, 'r') as f: + config = f.read() + configStr = '[ifupdown2]\n' + config + configFP = StringIO.StringIO(configStr) + parser = ConfigParser.RawConfigParser() + 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 + try: + op = None + if argv[0].endswith('ifup'): + op = 'up' + elif argv[0].endswith('ifdown'): + op = 'down' + elif argv[0].endswith('ifquery'): + op = 'query' + elif argv[0].endswith('ifreload'): + op = 'reload' + else: + print ('Unexpected executable.' + + ' Should be \'ifup\' or \'ifdown\' or \'ifquery\'') + exit(1) + # Command line arg parser + args = parse_args(argv[1:], op) + if not validate_args(op, args): + exit(1) + + if not sys.argv[0].endswith('ifquery') and not os.geteuid() == 0: + print 'error: must be root to run this command' + exit(1) + + if not sys.argv[0].endswith('ifquery') and not utils.lockFile(lockfile): + print 'Another instance of this program is already running.' + exit(0) + + read_config(args) + init(args) + handlers.get(op)(args) + except Exception, e: + if not str(e): + exit(1) + if args and args.debug: + raise + else: + if logger: + logger.error(str(e)) + else: + print str(e) + #if args and not args.debug: + # print '\nrerun the command with \'-d\' for a detailed errormsg' + exit(1) + finally: + deinit() + +if __name__ == "__main__": + + # required during boot + os.putenv('PATH', ENVPATH) + resource.setrlimit(resource.RLIMIT_CORE, (0,0)) + main(sys.argv) diff --git a/sbin/start-networking b/sbin/start-networking new file mode 100755 index 0000000..6ac8fc5 --- /dev/null +++ b/sbin/start-networking @@ -0,0 +1,168 @@ +#!/bin/bash + +# This replaces the old init.d script, and is run from the networking.service +# Only has start, stop, reload, because that's all systemd has. +# restart is implemented in systemd by stop then start. + +PATH="/sbin:/bin" +RUN_DIR="/run/network" +IFSTATE_LOCKFILE="${RUN_DIR}/ifstatelock" + +STATE_DIR="/var/tmp/network" +IFSTATE_FILE="${STATE_DIR}/ifstatenew" + +NAME=networking + +[ -x /sbin/ifup ] || exit 0 +[ -x /sbin/ifdown ] || exit 0 + +CONFIGURE_INTERFACES=yes + +EXTRA_ARGS= + +[ -f /etc/default/networking ] && . /etc/default/networking + +[ "$VERBOSE" = yes ] && EXTRA_ARGS=-v +[ "$DEBUG" = yes ] && EXTRA_ARGS="$EXTRA_ARGS -d" +[ "$SYSLOG" = yes ] && EXTRA_ARGS="$EXTRA_ARGS --syslog" + +perf_options() { + # At bootup lets set perfmode + [ -f ${IFSTATE_LOCKFILE} ] && echo -n "" && return + + echo -n "--perfmode" +} + +process_exclusions() { + set -- $EXCLUDE_INTERFACES + exclusions="" + for d + do + exclusions="-X $d $exclusions" + done + echo $exclusions +} + +check_network_file_systems() { + [ -e /proc/mounts ] || return 0 + + if [ -e /etc/iscsi/iscsi.initramfs ]; then + echo ${NAME}':' "not deconfiguring network interfaces: iSCSI root is mounted." + exit 0 + fi + + while read DEV MTPT FSTYPE REST; do + case $DEV in + /dev/nbd*|/dev/nd[a-z]*|/dev/etherd/e*) + echo ${NAME}':' "not deconfiguring network interfaces: network devices still mounted." + exit 0 + ;; + esac + case $FSTYPE in + nfs|nfs4|smbfs|ncp|ncpfs|cifs|coda|ocfs2|gfs|pvfs|pvfs2|fuse.httpfs|fuse.curlftpfs) + echo ${NAME}':' "not deconfiguring network interfaces: network file systems still mounted." + exit 0 + ;; + esac + done < /proc/mounts +} + +check_network_swap() { + [ -e /proc/swaps ] || return 0 + + while read DEV MTPT FSTYPE REST; do + case $DEV in + /dev/nbd*|/dev/nd[a-z]*|/dev/etherd/e*) + echo ${NAME}':' "not deconfiguring network interfaces: network swap still mounted." + exit 0 + ;; + esac + done < /proc/swaps +} + +ifup_hotplug () { + if [ -d /sys/class/net ] + then + ifaces=$(for iface in $(ifquery --list --allow=hotplug 2>/dev/null) + do + link=${iface##:*} + link=${link##.*} + if [ -e "/sys/class/net/$link" ] && [ "$(cat /sys/class/net/$link/operstate)" = up ] + then + echo "$iface" + fi + done) + if [ -n "$ifaces" ] + then + ifup $ifaces "$@" || true + fi + fi +} + +ifup_mgmt () { + ifaces=$(ifquery --list --allow=mgmt 2>/dev/null) + if [ -n "$ifaces" ]; then + echo "bringing up mgmt class interfaces" + ifup --allow=mgmt + fi +} + +ifupdown_init() { + # remove state file at boot + [ ! -e ${IFSTATE_LOCKFILE} ] && rm -f ${IFSTATE_FILE} + + [ ! -e /run/network ] && mkdir -p /run/network &>/dev/null + [ ! -e /etc/network/run ] && \ + ln -sf /run/network /etc/network/run &>/dev/null +} + +case "$1" in +start) + ifupdown_init + if [ "$CONFIGURE_INTERFACES" = no ] + then + echo ${NAME}':' "Not configuring network interfaces, see /etc/default/networking" + exit 0 + fi + set -f + exclusions=$(process_exclusions) + perfoptions=$(perf_options) + echo ${NAME}':' "Configuring network interfaces" + ifup_mgmt + ifup -a $EXTRA_ARGS $exclusions $perfoptions + ifup_hotplug $HOTPLUG_ARGS $EXTRA_ARGS $exclusions + ;; +stop) + if [ "$SKIP_DOWN_AT_SYSRESET" = "yes" ]; then + SYSRESET=0 + systemctl list-jobs | egrep -q '(shutdown|reboot|halt|poweroff)\.target' + [ $? -eq 0 ] && SYSRESET=1 + if [ $SYSRESET -eq 1 ]; then + echo ${NAME}':' "Skipping deconfiguring network interfaces" + exit 0 + fi + fi + ifupdown_init + check_network_file_systems + check_network_swap + exclusions=$(process_exclusions) + + echo ${NAME}':' "Deconfiguring network interfaces" + ifdown -a $EXTRA_ARGS + ;; + +reload) + + ifupdown_init + echo ${NAME}':' "Reloading network interfaces configuration" + + ifreload -a $EXTRA_ARGS + ;; + +*) + echo ${NAME}':' "Usage: $0 {start|stop|reload}" + exit 1 + ;; +esac + +exit 0 diff --git a/setup.py b/setup.py index 015a550..464a72a 100755 --- a/setup.py +++ b/setup.py @@ -1,47 +1,32 @@ from distutils.core import setup setup(name='ifupdown2', - version='0.1', + version='1.1', description = "ifupdown 2", author='Roopa Prabhu', author_email='roopa@cumulusnetworks.com', url='cumulusnetworks.com', packages=['ifupdown', 'ifupdownaddons'], - scripts = ['sbin/ifupdown'], - 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/ifupdown-addons-interfaces.5']), - ('/etc/init.d/', - ['init.d/networking']), - ('/sbin/', ['sbin/ifupdown']), - ('/etc/network/ifupdown2/', + data_files=[ ('/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', - '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', + ('/usr/share/ifupdown2/addons/', ['addons/bridge.py', + 'addons/bond.py', 'addons/vlan.py', 'addons/mstpctl.py', 'addons/address.py', 'addons/dhcp.py', 'addons/usercmds.py', - 'addons/ethtool.py', 'addons/loopback.py', + 'addons/ethtool.py', 'addons/addressvirtual.py', 'addons/vxlan.py', + 'addons/link.py', 'addons/vrf.py', 'addons/bridgevlan.py']), - ('/var/lib/ifupdownaddons/', ['config/addons.conf']), - ('/var/lib/ifupdownaddons/policy.d/', []), + ('/usr/share/ifupdown2/nlmanager/', + ['nlmanager/nllistener.py', + 'nlmanager/nlmanager.py', + 'nlmanager/nlpacket.py', + 'nlmanager/__init__.py', + 'nlmanager/README']), + ('/etc/network/ifupdown2/', ['config/addons.conf']), + ('/etc/network/ifupdown2/', ['config/addons.conf']), + ('/var/lib/ifupdown2/policy.d/', []), ('/etc/network/ifupdown2/policy.d/', []) ] )