[Python-checkins] bpo-39991: Enhance uuid parser for MAC address (GH-19045)

Victor Stinner webhook-mailer at python.org
Tue Mar 17 13:36:52 EDT 2020


https://github.com/python/cpython/commit/ebf6bb9f5ef032d1646b418ebbb645ea0b217da6
commit: ebf6bb9f5ef032d1646b418ebbb645ea0b217da6
branch: master
author: Victor Stinner <vstinner at python.org>
committer: GitHub <noreply at github.com>
date: 2020-03-17T18:36:44+01:00
summary:

bpo-39991: Enhance uuid parser for MAC address (GH-19045)

Reject valid IPv6 addresses which doesn't contain "::" but have
a length of 17 characters.

files:
M Lib/test/test_uuid.py
M Lib/uuid.py

diff --git a/Lib/test/test_uuid.py b/Lib/test/test_uuid.py
index 3b013a8ef5ebf..27fc56d226c08 100644
--- a/Lib/test/test_uuid.py
+++ b/Lib/test/test_uuid.py
@@ -679,6 +679,58 @@ class TestUUIDWithExtModule(BaseTestUUID, unittest.TestCase):
 class BaseTestInternals:
     _uuid = py_uuid
 
+    def check_parse_mac(self, aix):
+        if not aix:
+            patch = mock.patch.multiple(self.uuid,
+                                        _MAC_DELIM=b':',
+                                        _MAC_OMITS_LEADING_ZEROES=False)
+        else:
+            patch = mock.patch.multiple(self.uuid,
+                                        _MAC_DELIM=b'.',
+                                        _MAC_OMITS_LEADING_ZEROES=True)
+
+        with patch:
+            # Valid MAC addresses
+            if not aix:
+                tests = (
+                    (b'52:54:00:9d:0e:67', 0x5254009d0e67),
+                    (b'12:34:56:78:90:ab', 0x1234567890ab),
+                )
+            else:
+                # AIX format
+                tests = (
+                    (b'fe.ad.c.1.23.4', 0xfead0c012304),
+                )
+            for mac, expected in tests:
+                self.assertEqual(self.uuid._parse_mac(mac), expected)
+
+            # Invalid MAC addresses
+            for mac in (
+                b'',
+                # IPv6 addresses with same length than valid MAC address
+                # (17 characters)
+                b'fe80::5054:ff:fe9',
+                b'123:2:3:4:5:6:7:8',
+                # empty 5rd field
+                b'52:54:00:9d::67',
+                # only 5 fields instead of 6
+                b'52:54:00:9d:0e'
+                # invalid character 'x'
+                b'52:54:00:9d:0e:6x'
+                # dash separator
+                b'52-54-00-9d-0e-67',
+            ):
+                if aix:
+                    mac = mac.replace(b':', b'.')
+                with self.subTest(mac=mac):
+                    self.assertIsNone(self.uuid._parse_mac(mac))
+
+    def test_parse_mac(self):
+        self.check_parse_mac(False)
+
+    def test_parse_mac_aix(self):
+        self.check_parse_mac(True)
+
     def test_find_under_heading(self):
         data = '''\
 Name  Mtu   Network     Address           Ipkts Ierrs    Opkts Oerrs  Coll
diff --git a/Lib/uuid.py b/Lib/uuid.py
index 3b3abc2a45543..2799c75ba6a1a 100644
--- a/Lib/uuid.py
+++ b/Lib/uuid.py
@@ -434,6 +434,34 @@ def _find_mac_near_keyword(command, args, keywords, get_word_index):
     return first_local_mac or None
 
 
+def _parse_mac(word):
+    # Accept 'HH:HH:HH:HH:HH:HH' MAC address (ex: '52:54:00:9d:0e:67'),
+    # but reject IPv6 address (ex: 'fe80::5054:ff:fe9' or '123:2:3:4:5:6:7:8').
+    #
+    # Virtual interfaces, such as those provided by VPNs, do not have a
+    # colon-delimited MAC address as expected, but a 16-byte HWAddr separated
+    # by dashes. These should be ignored in favor of a real MAC address
+    parts = word.split(_MAC_DELIM)
+    if len(parts) != 6:
+        return
+    if _MAC_OMITS_LEADING_ZEROES:
+        # (Only) on AIX the macaddr value given is not prefixed by 0, e.g.
+        # en0   1500  link#2      fa.bc.de.f7.62.4 110854824     0 160133733     0     0
+        # not
+        # en0   1500  link#2      fa.bc.de.f7.62.04 110854824     0 160133733     0     0
+        if not all(1 <= len(part) <= 2 for part in parts):
+            return
+        hexstr = b''.join(part.rjust(2, b'0') for part in parts)
+    else:
+        if not all(len(part) == 2 for part in parts):
+            return
+        hexstr = b''.join(parts)
+    try:
+        return int(hexstr, 16)
+    except ValueError:
+        return
+
+
 def _find_mac_under_heading(command, args, heading):
     """Looks for a MAC address under a heading in a command's output.
 
@@ -453,39 +481,21 @@ def _find_mac_under_heading(command, args, heading):
 
     first_local_mac = None
     for line in stdout:
+        words = line.rstrip().split()
         try:
-            words = line.rstrip().split()
             word = words[column_index]
-            # Accept 'HH:HH:HH:HH:HH:HH' MAC address (ex: '52:54:00:9d:0e:67'),
-            # but reject IPv6 address (ex: 'fe80::5054:ff:fe9') detected
-            # by '::' pattern.
-            if len(word) == 17 and b'::' not in word:
-                mac = int(word.replace(_MAC_DELIM, b''), 16)
-            elif _MAC_OMITS_LEADING_ZEROES:
-                # (Only) on AIX the macaddr value given is not prefixed by 0, e.g.
-                # en0   1500  link#2      fa.bc.de.f7.62.4 110854824     0 160133733     0     0
-                # not
-                # en0   1500  link#2      fa.bc.de.f7.62.04 110854824     0 160133733     0     0
-                parts = word.split(_MAC_DELIM)
-                if len(parts) == 6 and all(0 < len(p) <= 2 for p in parts):
-                    hexstr = b''.join(p.rjust(2, b'0') for p in parts)
-                    mac = int(hexstr, 16)
-                else:
-                    continue
-            else:
-                continue
-        except (ValueError, IndexError):
-            # Virtual interfaces, such as those provided by
-            # VPNs, do not have a colon-delimited MAC address
-            # as expected, but a 16-byte HWAddr separated by
-            # dashes. These should be ignored in favor of a
-            # real MAC address
-            pass
-        else:
-            if _is_universal(mac):
-                return mac
-            first_local_mac = first_local_mac or mac
-    return first_local_mac or None
+        except IndexError:
+            continue
+
+        mac = _parse_mac(word)
+        if mac is None:
+            continue
+        if _is_universal(mac):
+            return mac
+        if first_local_mac is None:
+            first_local_mac = mac
+
+    return first_local_mac
 
 
 # The following functions call external programs to 'get' a macaddr value to



More information about the Python-checkins mailing list