]> git.proxmox.com Git - mirror_ifupdown2.git/blobdiff - ifupdown2/ifupdownaddons/LinkUtils.py
addons: when comparing mac addresses use integer representation
[mirror_ifupdown2.git] / ifupdown2 / ifupdownaddons / LinkUtils.py
index 53eefd94909a55432282cd0371c02e5e105a1824..8078093e6dcc20ead410539de92dff3eb7e63b2d 100644 (file)
@@ -10,8 +10,10 @@ import re
 import glob
 import shlex
 import signal
+import socket
 import subprocess
 
+from string import maketrans
 from ipaddr import IPNetwork, IPv6Network
 
 try:
@@ -58,6 +60,8 @@ class LinkUtils(utilsBase):
     DEFAULT_IP_METRIC = 1024
     ADDR_METRIC_SUPPORT = None
 
+    mac_translate_tab = maketrans(":.-,", "    ")
+
     def __init__(self, *args, **kargs):
         utilsBase.__init__(self, *args, **kargs)
 
@@ -84,6 +88,14 @@ class LinkUtils(utilsBase):
                 LinkUtils.ADDR_METRIC_SUPPORT = False
                 self.logger.info('address metric support: KO')
 
+    @classmethod
+    def mac_str_to_int(cls, mac):
+        mac_int = 0
+        if mac:
+            for n in mac.translate(cls.mac_translate_tab).split():
+                mac_int += int(n, 16)
+        return mac_int
+
     @classmethod
     def addr_metric_support(cls):
         return cls.ADDR_METRIC_SUPPORT
@@ -230,41 +242,38 @@ class LinkUtils(utilsBase):
         battrs = {}
         bports = {}
 
-        brout = utils.exec_command('%s showstp %s' % (utils.brctl_cmd, bridgename))
-        chunks = re.split(r'\n\n', brout, maxsplit=0, flags=re.MULTILINE)
-
         try:
             # Get all bridge attributes
-            broutlines = chunks[0].splitlines()
             # battrs['pathcost'] = broutlines[3].split('path cost')[1].strip()
 
             try:
-                battrs['maxage'] = broutlines[4].split('bridge max age')[
-                    1].strip().replace('.00', '')
+                battrs['maxage'] = self.read_file_oneline(
+                    '/sys/class/net/%s/bridge/max_age' % bridgename)
             except:
                 pass
 
+
             try:
-                battrs['hello'] = broutlines[5].split('bridge hello time')[
-                    1].strip().replace('.00', '')
+                battrs['hello'] = self.read_file_oneline(
+                    '/sys/class/net/%s/bridge/hello_time' % bridgename)
             except:
                 pass
 
             try:
-                battrs['fd'] = broutlines[6].split('bridge forward delay')[
-                    1].strip().replace('.00', '')
+                battrs['fd'] = self.read_file_oneline(
+                    '/sys/class/net/%s/bridge/forward_delay' % bridgename)
             except:
                 pass
 
             try:
-                battrs['ageing'] = broutlines[7].split('ageing time')[
-                    1].strip().replace('.00', '')
+                battrs['ageing'] = self.read_file_oneline(
+                    '/sys/class/net/%s/bridge/ageing_time' % bridgename)
             except:
                 pass
 
             try:
-                battrs['mcrouter'] = broutlines[12].split('mc router')[
-                    1].strip().split('\t\t\t')[0]
+                battrs['mcrouter'] = self.read_file_oneline(
+                    '/sys/class/net/%s/bridge/multicast_router' % bridgename)
             except:
                 pass
 
@@ -288,30 +297,24 @@ class LinkUtils(utilsBase):
 
                 # XXX: comment this out until mc attributes become available
                 # with brctl again
-                # battrs['hashel'] = broutlines[10].split('hash elasticity')[1].split()[0].strip()
-                # battrs['hashmax'] = broutlines[10].split('hash max')[1].strip()
-                # battrs['mclmc'] = broutlines[11].split('mc last member count')[1].split()[0].strip()
+
                 # battrs['mciqc'] = broutlines[11].split('mc init query count')[1].strip()
-                # battrs['mcrouter'] = broutlines[12].split('mc router')[1].split()[0].strip()
-                ##battrs['mcsnoop'] = broutlines[12].split('mc snooping')[1].strip()
                 # battrs['mclmt'] = broutlines[13].split('mc last member timer')[1].split()[0].strip()
         except Exception, e:
             self.logger.warn('%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] == ' ':
-                continue
-            bplines = bpout.splitlines()
-            pname = bplines[0].split()[0]
+
+        names = [os.path.basename(x) for x in glob.glob("/sys/class/net/%s/brif/*" % bridgename)]
+        for pname in names:
             bportattrs = {}
             try:
-                bportattrs['pathcost'] = bplines[2].split(
-                    'path cost')[1].strip()
-                bportattrs['fdelay'] = bplines[4].split(
-                    'forward delay timer')[1].strip()
+
+                bportattrs['pathcost'] = self.read_file_oneline(
+                    '/sys/class/net/%s/brport/path_cost' % pname)
+                bportattrs['fdelay'] = self.read_file_oneline(
+                    '/sys/class/net/%s/brport/forward_delay_timer' % pname)
                 bportattrs['portmcrouter'] = self.read_file_oneline(
                     '/sys/class/net/%s/brport/multicast_router' % pname)
                 bportattrs['portmcfl'] = self.read_file_oneline(
@@ -326,8 +329,12 @@ class LinkUtils(utilsBase):
                     '/sys/class/net/%s/brport/learning' % pname)
                 bportattrs['arp-nd-suppress'] = self.read_file_oneline(
                     '/sys/class/net/%s/brport/neigh_suppress' % pname)
-                # bportattrs['mcrouters'] = bplines[6].split('mc router')[1].split()[0].strip()
-                # bportattrs['mc fast leave'] = bplines[6].split('mc fast leave')[1].strip()
+
+                #bportattrs['mcrouters'] = self.read_file_oneline(
+                #    '/sys/class/net/%s/brport/multicast_router' % pname)
+                #bportattrs['mc fast leave'] = self.read_file_oneline(
+                #    '/sys/class/net/%s/brport/multicast_fast_leave' % pname)                                                          
+
             except Exception, e:
                 self.logger.warn('%s: error while processing bridge attributes: %s' % (bridgename, str(e)))
             bports[pname] = bportattrs
@@ -544,6 +551,8 @@ class LinkUtils(utilsBase):
                                 tunattrs['ttl'] = citems[j + 1]
                             elif citems[j] == 'dev':
                                 tunattrs['physdev'] = citems[j + 1]
+                            elif citems[j] in ['vti', 'vti6', 'ip6gre', 'ipip6', 'ip6ip6']:
+                                tunattrs['mode'] = citems[j]
                         linkattrs['linkinfo'] = tunattrs
                         break
                     elif citems[i] == 'link/ppp':
@@ -588,6 +597,8 @@ class LinkUtils(utilsBase):
                         break
                     elif citems[i] == 'macvlan' and citems[i + 1] == 'mode':
                         linkattrs['kind'] = 'macvlan'
+                    elif citems[i] == 'xfrm':
+                        linkattrs['kind'] = 'xfrm'
                 except Exception as e:
                     if warn:
                         self.logger.debug('%s: parsing error: id, mtu, state, '
@@ -697,6 +708,12 @@ class LinkUtils(utilsBase):
         [linkCache.update_attrdict([ifname], linkattrs)
          for ifname, linkattrs in linkout.items()]
 
+    def del_cache_entry(self, ifname):
+        try:
+            del linkCache.links[ifname]
+        except:
+            pass
+
     def cache_get(self, t, attrlist, refresh=False):
         return self._cache_get(t, attrlist, refresh)
 
@@ -867,11 +884,11 @@ class LinkUtils(utilsBase):
             return
         cmd = 'addr del %s' % address
         if broadcast:
-            cmd += 'broadcast %s' % broadcast
+            cmd += ' broadcast %s' % broadcast
         if peer:
-            cmd += 'peer %s' % peer
+            cmd += ' peer %s' % peer
         if scope:
-            cmd += 'scope %s' % scope
+            cmd += ' scope %s' % scope
         cmd += ' dev %s' % ifacename
         utils.exec_command('%s %s' % (utils.ip_cmd, cmd))
         self._cache_delete([ifacename, 'addrs', address])
@@ -931,23 +948,24 @@ class LinkUtils(utilsBase):
             interface_name = ifname
 
         if addr_virtual_ifaceobj:
-            for virtual in addr_virtual_ifaceobj.get_attr_value('address-virtual') or []:
-                for ip in virtual.split():
-                    try:
-                        IPNetwork(ip)
-                        config_addrs.add(ip)
-                    except:
-                        pass
-
-            saved_ifaceobjs = statemanager.statemanager_api.get_ifaceobjs(addr_virtual_ifaceobj.name)
-            for saved_ifaceobj in saved_ifaceobjs or []:
-                for virtual in saved_ifaceobj.get_attr_value('address-virtual') or []:
+            for attr_name in ["address-virtual", "vrrp"]:
+                for virtual in addr_virtual_ifaceobj.get_attr_value(attr_name) or []:
                     for ip in virtual.split():
                         try:
                             IPNetwork(ip)
                             config_addrs.add(ip)
                         except:
                             pass
+
+                saved_ifaceobjs = statemanager.statemanager_api.get_ifaceobjs(addr_virtual_ifaceobj.name)
+                for saved_ifaceobj in saved_ifaceobjs or []:
+                    for virtual in saved_ifaceobj.get_attr_value(attr_name) or []:
+                        for ip in virtual.split():
+                            try:
+                                IPNetwork(ip)
+                                config_addrs.add(ip)
+                            except:
+                                pass
         else:
             if ifaceobj:
                 for addr in ifaceobj.get_attr_value('address') or []:
@@ -1074,8 +1092,11 @@ class LinkUtils(utilsBase):
 
     def link_set_hwaddress(self, ifacename, hwaddress, force=False):
         if not force:
-            if self._cache_check('link', [ifacename, 'hwaddress'], hwaddress):
-                return
+            link_hwaddress = self.link_get_hwaddress(ifacename)
+
+            if self.mac_str_to_int(link_hwaddress) == self.mac_str_to_int(hwaddress):
+                return False
+
         self.link_down(ifacename)
         cmd = 'link set dev %s address %s' % (ifacename, hwaddress)
         if LinkUtils.ipbatch:
@@ -1084,6 +1105,7 @@ class LinkUtils(utilsBase):
             utils.exec_command('%s %s' % (utils.ip_cmd, cmd))
         self.link_up(ifacename)
         self._cache_update([ifacename, 'hwaddress'], hwaddress)
+        return True
 
     def link_set_mtu(self, ifacename, mtu):
         if ifupdownflags.flags.DRYRUN:
@@ -1112,7 +1134,7 @@ class LinkUtils(utilsBase):
         return self._cache_get('link', [ifacename, 'ifflag'], refresh=True)
 
     @staticmethod
-    def route_add_gateway(ifacename, gateway, vrf=None, metric=None):
+    def route_add_gateway(ifacename, gateway, vrf=None, metric=None, onlink=True):
         if not gateway:
             return
         if not vrf:
@@ -1123,8 +1145,12 @@ class LinkUtils(utilsBase):
                    (utils.ip_cmd, vrf, gateway))
         # Add metric
         if metric:
-            cmd += 'metric %s' % metric
+            cmd += ' metric %s' % metric
         cmd += ' dev %s' % ifacename
+
+        if onlink:
+            cmd += " onlink"
+
         utils.exec_command(cmd)
 
     @staticmethod
@@ -1263,15 +1289,18 @@ class LinkUtils(utilsBase):
 
         cmd = ''
         if '6' in mode:
-            cmd = ' -6 '
+            cmd = ' -6'
+
+        if mode in ['gretap']:
+            cmd += ' link add %s type %s' % (tunnelname, mode)
+        else:
+            cmd += ' tunnel add %s mode %s' % (tunnelname, mode)
 
-        cmd += 'tunnel add'
-        cmd += ' %s mode %s' %(tunnelname, mode)
         if attrs:
             for k, v in attrs.iteritems():
-                cmd += ' %s' %k
+                cmd += ' %s' % k
                 if v:
-                    cmd += ' %s' %v
+                    cmd += ' %s' % v
         if self.ipbatch and not self.ipbatch_pause:
             self.add_to_batch(cmd)
         else:
@@ -1293,7 +1322,6 @@ class LinkUtils(utilsBase):
             self.add_to_batch(cmd)
         else:
             utils.exec_command('ip %s' % cmd)
-        self._cache_update([tunnelname], {})
 
     def link_create_vxlan(self, name, vxlanid,
                           localtunnelip=None,
@@ -1301,7 +1329,8 @@ class LinkUtils(utilsBase):
                           remoteips=None,
                           learning='on',
                           ageing=None,
-                          anycastip=None):
+                          anycastip=None,
+                          ttl=None):
         if svcnodeip and remoteips:
             raise Exception("svcnodeip and remoteip is mutually exclusive")
         args = ''
@@ -1311,6 +1340,8 @@ class LinkUtils(utilsBase):
             args += ' ageing %s' % ageing
         if learning == 'off':
             args += ' nolearning'
+        if ttl is not None:
+            args += ' ttl %s' % ttl
 
         if self.link_exists(name):
             cmd = 'link set dev %s type vxlan dstport %d' % (name, LinkUtils.VXLAN_UDP_PORT)
@@ -1345,6 +1376,10 @@ class LinkUtils(utilsBase):
             return True
         return os.path.exists('/sys/class/net/%s' % ifacename)
 
+    @staticmethod
+    def link_exists_nodryrun(ifname):
+        return os.path.exists('/sys/class/net/%s' % ifname)
+
     def link_get_ifindex(self, ifacename):
         if ifupdownflags.flags.DRYRUN:
             return True
@@ -1356,8 +1391,12 @@ class LinkUtils(utilsBase):
         return False
 
     @staticmethod
-    def link_add_macvlan(ifname, macvlan_ifacename):
-        utils.exec_commandl(['ip', 'link', 'add',  'link', ifname, 'name', macvlan_ifacename, 'type', 'macvlan', 'mode', 'private'])
+    def link_add_macvlan(ifname, macvlan_ifacename, mode):
+        utils.exec_commandl(['ip', 'link', 'add',  'link', ifname, 'name', macvlan_ifacename, 'type', 'macvlan', 'mode', mode])
+
+    @staticmethod
+    def link_add_xfrm(ifname, xfrm_name, xfrm_id):
+        utils.exec_commandl(['ip', 'link', 'add', xfrm_name, 'type', 'xfrm', 'dev', ifname, 'if_id', xfrm_id])
 
     @staticmethod
     def route_add(route):
@@ -1538,11 +1577,40 @@ class LinkUtils(utilsBase):
 
         if not bridgeout: return brvlaninfo
         try:
-            vlan_json_dict = json.loads(bridgeout, encoding="utf-8")
+            vlan_json = json.loads(bridgeout, encoding="utf-8")
         except Exception, e:
             self.logger.info('json loads failed with (%s)' % str(e))
             return {}
-        return vlan_json_dict
+
+        try:
+            if isinstance(vlan_json, list):
+                # newer iproute2 version changed the bridge vlan show output
+                # ifupdown2 relies on the previous format, we have the convert
+                # data into old format
+                bridge_port_vids = dict()
+
+                for intf in vlan_json:
+                    bridge_port_vids[intf["ifname"]] = intf["vlans"]
+
+                return bridge_port_vids
+            else:
+                # older iproute2 version have different ways to dump vlans
+                # ifupdown2 prefers the following syntax:
+                # {
+                #    "vx-1002": [{
+                #        "vlan": 1002,
+                #        "flags": ["PVID", "Egress Untagged"]
+                #    }
+                #    ],
+                #    "vx-1004": [{
+                #        "vlan": 1004,
+                #        "flags": ["PVID", "Egress Untagged"]
+                #    }]
+                # }
+                return vlan_json
+        except Exception as e:
+            self.logger.debug("bridge vlan show: Unknown json output: %s" % str(e))
+            return vlan_json
 
     @staticmethod
     def get_bridge_vlan_nojson():
@@ -2590,6 +2658,31 @@ class LinkUtils(utilsBase):
             return mcqv4src.get(vlan)
         return mcqv4src
 
+    def bridge_get_mcqv4src_sysfs(self, bridge, vlan=None):
+        if not LinkUtils.bridge_utils_is_installed:
+            return {}
+        if not self.supported_command['showmcqv4src']:
+            return {}
+        if ifupdownflags.flags.PERFMODE:
+            return {}
+        mcqv4src = {}
+        try:
+            filename = '/sys/class/net/%s/bridge/multicast_v4_queriers' % bridge
+            if os.path.exists(filename):
+                for line in self.read_file(filename) or []:
+                    vlan_id, ip = line.split('=')
+                    mcqv4src[vlan_id] = ip.strip()
+        except Exception as e:
+            s = str(e).lower()
+            msg = ('%s showmcqv4src: skipping unsupported command'
+                   % utils.brctl_cmd)
+            self.logger.info(msg)
+            self.supported_command['showmcqv4src'] = False
+            return {}
+        if vlan:
+            return mcqv4src.get(vlan)
+        return mcqv4src
+
     @staticmethod
     def bridge_set_mclmi(bridge, mclmi):
         if not LinkUtils.bridge_utils_is_installed:
@@ -2634,18 +2727,63 @@ class LinkUtils(utilsBase):
         except:
             return []
 
-    def ipv6_addrgen(self, ifname, addrgen):
-        cmd = 'link set dev %s addrgenmode %s' % (ifname, 'eui64' if addrgen else 'none')
+    def reset_addr_cache(self, ifname):
+        try:
+            linkCache.links[ifname]['addrs'] = {}
+            self.logger.debug('%s: reset address cache' % ifname)
+        except:
+            pass
+
+    def get_ipv6_addrgen_mode(self, ifname):
+        try:
+            return self._cache_get('link', [ifname, 'af_spec', socket.AF_INET6])[Link.IFLA_INET6_ADDR_GEN_MODE]
+        except:
+            # default to 0 (eui64)
+            return 0
+
+    def ipv6_addrgen(self, ifname, addrgen, link_created):
+        try:
+            # IFLA_INET6_ADDR_GEN_MODE values:
+            # 0 = eui64
+            # 1 = none
+            if self._link_cache_get([ifname, 'af_spec', socket.AF_INET6])[Link.IFLA_INET6_ADDR_GEN_MODE] == addrgen:
+                self.logger.debug('%s: ipv6 addrgen already %s' % (ifname, 'off' if addrgen else 'on'))
+                return
+
+            disabled_ipv6 = self.read_file_oneline('/proc/sys/net/ipv6/conf/%s/disable_ipv6' % ifname)
+            if not disabled_ipv6 or int(disabled_ipv6) == 1:
+                self.logger.info('%s: cannot set addrgen: ipv6 is disabled on this device' % ifname)
+                return
+
+            if int(self._link_cache_get([ifname, 'mtu'])) < 1280:
+                self.logger.info('%s: ipv6 addrgen is disabled on device with MTU '
+                                 'lower than 1280: cannot set addrgen %s' % (ifname, 'off' if addrgen else 'on'))
+                return
+        except (KeyError, TypeError):
+            self.logger.debug('%s: ipv6 addrgen probably not supported or disabled on this device' % ifname)
+            return
+        except Exception:
+            pass
+
+        if not link_created:
+            # When setting addrgenmode it is necessary to flap the macvlan
+            # device. After flapping the device we also need to re-add all
+            # the user configuration. The best way to add the user config
+            # is to flush our internal address cache
+            self.reset_addr_cache(ifname)
+
+        cmd = 'link set dev %s addrgenmode %s' % (ifname, Link.ifla_inet6_addr_gen_mode_dict.get(addrgen))
 
         is_link_up = self.is_link_up(ifname)
 
         if is_link_up:
             self.link_down(ifname)
 
-        if LinkUtils.ipbatch:
-            self.add_to_batch(cmd)
-        else:
-            utils.exec_command('%s %s' % (utils.ip_cmd, cmd))
+        #if LinkUtils.ipbatch:
+        #    self.add_to_batch(cmd)
+        #else:
+        # because this command might fail on older kernel its better to not batch it
+        utils.exec_command('%s %s' % (utils.ip_cmd, cmd))
 
         if is_link_up:
             self.link_up(ifname)