diff options
Diffstat (limited to 'Lib')
-rw-r--r-- | Lib/test/test_uuid.py | 47 | ||||
-rw-r--r-- | Lib/uuid.py | 70 |
2 files changed, 28 insertions, 89 deletions
diff --git a/Lib/test/test_uuid.py b/Lib/test/test_uuid.py index 54d73f7..083c2aa 100644 --- a/Lib/test/test_uuid.py +++ b/Lib/test/test_uuid.py @@ -512,69 +512,60 @@ eth0 Link encap:Ethernet HWaddr 12:34:56:78:90:ab self.assertEqual(mac, 0x1234567890ab) - def check_node(self, node, requires=None, *, check_bit=True): + def check_node(self, node, requires=None, network=False): if requires and node is None: self.skipTest('requires ' + requires) hex = '%012x' % node if support.verbose >= 2: print(hex, end=' ') - # The MAC address will be universally administered (i.e. the second - # least significant bit of the first octet must be unset) for any - # physical interface, such as an ethernet port or wireless adapter. - # There are some cases where this won't be the case. Randomly - # generated MACs may not be universally administered, but they must - # have their multicast bit set, though this is tested in the - # `test_random_getnode()` method specifically. Another case is the - # Travis-CI case, which apparently only has locally administered MAC - # addresses. - if check_bit and not os.getenv('TRAVIS'): - self.assertFalse(node & (1 << 41), '%012x' % node) + if network: + # 47 bit will never be set in IEEE 802 addresses obtained + # from network cards. + self.assertFalse(node & 0x010000000000, hex) self.assertTrue(0 < node < (1 << 48), "%s is not an RFC 4122 node ID" % hex) @unittest.skipUnless(os.name == 'posix', 'requires Posix') def test_ifconfig_getnode(self): node = self.uuid._ifconfig_getnode() - self.check_node(node, 'ifconfig') + self.check_node(node, 'ifconfig', True) @unittest.skipUnless(os.name == 'posix', 'requires Posix') def test_ip_getnode(self): node = self.uuid._ip_getnode() - self.check_node(node, 'ip') + self.check_node(node, 'ip', True) @unittest.skipUnless(os.name == 'posix', 'requires Posix') def test_arp_getnode(self): node = self.uuid._arp_getnode() - self.check_node(node, 'arp') + self.check_node(node, 'arp', True) @unittest.skipUnless(os.name == 'posix', 'requires Posix') def test_lanscan_getnode(self): node = self.uuid._lanscan_getnode() - self.check_node(node, 'lanscan') + self.check_node(node, 'lanscan', True) @unittest.skipUnless(os.name == 'posix', 'requires Posix') def test_netstat_getnode(self): node = self.uuid._netstat_getnode() - self.check_node(node, 'netstat') + self.check_node(node, 'netstat', True) @unittest.skipUnless(os.name == 'nt', 'requires Windows') def test_ipconfig_getnode(self): node = self.uuid._ipconfig_getnode() - self.check_node(node, 'ipconfig') + self.check_node(node, 'ipconfig', True) @unittest.skipUnless(importable('win32wnet'), 'requires win32wnet') @unittest.skipUnless(importable('netbios'), 'requires netbios') def test_netbios_getnode(self): node = self.uuid._netbios_getnode() - self.check_node(node) + self.check_node(node, network=True) def test_random_getnode(self): node = self.uuid._random_getnode() - # The multicast bit, i.e. the least significant bit of first octet, - # must be set for randomly generated MAC addresses. See RFC 4122, - # $4.1.6. - self.assertTrue(node & (1 << 40), '%012x' % node) - self.check_node(node, check_bit=False) + # Least significant bit of first octet must be set. + self.assertTrue(node & 0x010000000000, '%012x' % node) + self.check_node(node) @unittest.skipUnless(os.name == 'posix', 'requires Posix') def test_unix_getnode(self): @@ -584,17 +575,13 @@ eth0 Link encap:Ethernet HWaddr 12:34:56:78:90:ab node = self.uuid._unix_getnode() except TypeError: self.skipTest('requires uuid_generate_time') - # Since we don't know the provenance of the MAC address, don't check - # whether it is locally or universally administered. - self.check_node(node, 'unix', check_bit=False) + self.check_node(node, 'unix') @unittest.skipUnless(os.name == 'nt', 'requires Windows') @unittest.skipUnless(importable('ctypes'), 'requires ctypes') def test_windll_getnode(self): node = self.uuid._windll_getnode() - # Since we don't know the provenance of the MAC address, don't check - # whether it is locally or universally administered. - self.check_node(node, check_bit=False) + self.check_node(node) class TestInternalsWithoutExtModule(BaseTestInternals, unittest.TestCase): diff --git a/Lib/uuid.py b/Lib/uuid.py index 9e7c672..020c6e7 100644 --- a/Lib/uuid.py +++ b/Lib/uuid.py @@ -342,29 +342,11 @@ def _popen(command, *args): env=env) return proc -# For MAC (a.k.a. IEEE 802, or EUI-48) addresses, the second least significant -# bit of the first octet signifies whether the MAC address is universally (0) -# or locally (1) administered. Network cards from hardware manufacturers will -# always be universally administered to guarantee global uniqueness of the MAC -# address, but any particular machine may have other interfaces which are -# locally administered. An example of the latter is the bridge interface to -# the Touch Bar on MacBook Pros. -# -# This bit works out to be the 42nd bit counting from 1 being the least -# significant, or 1<<41. We'll skip over any locally administered MAC -# addresses, as it makes no sense to use those in UUID calculation. -# -# See https://en.wikipedia.org/wiki/MAC_address#Universal_vs._local - -def _is_universal(mac): - return not (mac & (1 << 41)) - def _find_mac(command, args, hw_identifiers, get_index): - first_local_mac = None try: proc = _popen(command, *args.split()) if not proc: - return None + return with proc: for line in proc.stdout: words = line.lower().rstrip().split() @@ -373,9 +355,8 @@ def _find_mac(command, args, hw_identifiers, get_index): try: word = words[get_index(i)] mac = int(word.replace(b':', b''), 16) - if _is_universal(mac): + if mac: return mac - first_local_mac = first_local_mac or mac except (ValueError, IndexError): # Virtual interfaces, such as those provided by # VPNs, do not have a colon-delimited MAC address @@ -385,7 +366,6 @@ def _find_mac(command, args, hw_identifiers, get_index): pass except OSError: pass - return first_local_mac or None def _ifconfig_getnode(): """Get the hardware address on Unix by running ifconfig.""" @@ -395,7 +375,6 @@ def _ifconfig_getnode(): mac = _find_mac('ifconfig', args, keywords, lambda i: i+1) if mac: return mac - return None def _ip_getnode(): """Get the hardware address on Unix by running ip.""" @@ -403,7 +382,6 @@ def _ip_getnode(): mac = _find_mac('ip', 'link list', [b'link/ether'], lambda i: i+1) if mac: return mac - return None def _arp_getnode(): """Get the hardware address on Unix by running arp.""" @@ -426,10 +404,8 @@ def _arp_getnode(): # This works on Linux, FreeBSD and NetBSD mac = _find_mac('arp', '-an', [os.fsencode('(%s)' % ip_addr)], lambda i: i+2) - # Return None instead of 0. if mac: return mac - return None def _lanscan_getnode(): """Get the hardware address on Unix by running lanscan.""" @@ -439,36 +415,32 @@ def _lanscan_getnode(): def _netstat_getnode(): """Get the hardware address on Unix by running netstat.""" # This might work on AIX, Tru64 UNIX. - first_local_mac = None try: proc = _popen('netstat', '-ia') if not proc: - return None + return with proc: words = proc.stdout.readline().rstrip().split() try: i = words.index(b'Address') except ValueError: - return None + return for line in proc.stdout: try: words = line.rstrip().split() word = words[i] if len(word) == 17 and word.count(b':') == 5: mac = int(word.replace(b':', b''), 16) - if _is_universal(mac): + if mac: return mac - first_local_mac = first_local_mac or mac except (ValueError, IndexError): pass except OSError: pass - return first_local_mac or None def _ipconfig_getnode(): """Get the hardware address on Windows by running ipconfig.exe.""" import os, re - first_local_mac = None dirs = ['', r'c:\windows\system32', r'c:\winnt\system32'] try: import ctypes @@ -486,23 +458,18 @@ def _ipconfig_getnode(): for line in pipe: value = line.split(':')[-1].strip().lower() if re.match('([0-9a-f][0-9a-f]-){5}[0-9a-f][0-9a-f]', value): - mac = int(value.replace('-', ''), 16) - if _is_universal(mac): - return mac - first_local_mac = first_local_mac or mac - return first_local_mac or None + return int(value.replace('-', ''), 16) def _netbios_getnode(): """Get the hardware address on Windows using NetBIOS calls. See http://support.microsoft.com/kb/118623 for details.""" import win32wnet, netbios - first_local_mac = None ncb = netbios.NCB() ncb.Command = netbios.NCBENUM ncb.Buffer = adapters = netbios.LANA_ENUM() adapters._pack() if win32wnet.Netbios(ncb) != 0: - return None + return adapters._unpack() for i in range(adapters.length): ncb.Reset() @@ -521,11 +488,7 @@ def _netbios_getnode(): bytes = status.adapter_address[:6] if len(bytes) != 6: continue - mac = int.from_bytes(bytes, 'big') - if _is_universal(mac): - return mac - first_local_mac = first_local_mac or mac - return first_local_mac or None + return int.from_bytes(bytes, 'big') _generate_time_safe = _UuidCreate = None @@ -638,19 +601,9 @@ def _windll_getnode(): return UUID(bytes=bytes_(_buffer.raw)).node def _random_getnode(): - """Get a random node ID.""" - # RFC 4122, $4.1.6 says "For systems with no IEEE address, a randomly or - # pseudo-randomly generated value may be used; see Section 4.5. The - # multicast bit must be set in such addresses, in order that they will - # never conflict with addresses obtained from network cards." - # - # The "multicast bit" of a MAC address is defined to be "the least - # significant bit of the first octet". This works out to be the 41st bit - # counting from 1 being the least significant bit, or 1<<40. - # - # See https://en.wikipedia.org/wiki/MAC_address#Unicast_vs._multicast + """Get a random node ID, with eighth bit set as suggested by RFC 4122.""" import random - return random.getrandbits(48) | (1 << 40) + return random.getrandbits(48) | 0x010000000000 _node = None @@ -673,14 +626,13 @@ def getnode(): getters = [_unix_getnode, _ifconfig_getnode, _ip_getnode, _arp_getnode, _lanscan_getnode, _netstat_getnode] - for getter in getters: + for getter in getters + [_random_getnode]: try: _node = getter() except: continue if _node is not None: return _node - return _random_getnode() _last_timestamp = None |