Commit c9409f7c authored by Victor Stinner's avatar Victor Stinner Committed by GitHub

Revert "bpo-32107 - Better merge of #4494 (#4576)" (#4593)

This reverts commit 9522a218.
parent 39f0bb5a
...@@ -512,69 +512,60 @@ eth0 Link encap:Ethernet HWaddr 12:34:56:78:90:ab ...@@ -512,69 +512,60 @@ eth0 Link encap:Ethernet HWaddr 12:34:56:78:90:ab
self.assertEqual(mac, 0x1234567890ab) 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: if requires and node is None:
self.skipTest('requires ' + requires) self.skipTest('requires ' + requires)
hex = '%012x' % node hex = '%012x' % node
if support.verbose >= 2: if support.verbose >= 2:
print(hex, end=' ') print(hex, end=' ')
# The MAC address will be universally administered (i.e. the second if network:
# least significant bit of the first octet must be unset) for any # 47 bit will never be set in IEEE 802 addresses obtained
# physical interface, such as an ethernet port or wireless adapter. # from network cards.
# There are some cases where this won't be the case. Randomly self.assertFalse(node & 0x010000000000, hex)
# 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)
self.assertTrue(0 < node < (1 << 48), self.assertTrue(0 < node < (1 << 48),
"%s is not an RFC 4122 node ID" % hex) "%s is not an RFC 4122 node ID" % hex)
@unittest.skipUnless(os.name == 'posix', 'requires Posix') @unittest.skipUnless(os.name == 'posix', 'requires Posix')
def test_ifconfig_getnode(self): def test_ifconfig_getnode(self):
node = self.uuid._ifconfig_getnode() node = self.uuid._ifconfig_getnode()
self.check_node(node, 'ifconfig') self.check_node(node, 'ifconfig', True)
@unittest.skipUnless(os.name == 'posix', 'requires Posix') @unittest.skipUnless(os.name == 'posix', 'requires Posix')
def test_ip_getnode(self): def test_ip_getnode(self):
node = self.uuid._ip_getnode() node = self.uuid._ip_getnode()
self.check_node(node, 'ip') self.check_node(node, 'ip', True)
@unittest.skipUnless(os.name == 'posix', 'requires Posix') @unittest.skipUnless(os.name == 'posix', 'requires Posix')
def test_arp_getnode(self): def test_arp_getnode(self):
node = self.uuid._arp_getnode() node = self.uuid._arp_getnode()
self.check_node(node, 'arp') self.check_node(node, 'arp', True)
@unittest.skipUnless(os.name == 'posix', 'requires Posix') @unittest.skipUnless(os.name == 'posix', 'requires Posix')
def test_lanscan_getnode(self): def test_lanscan_getnode(self):
node = self.uuid._lanscan_getnode() node = self.uuid._lanscan_getnode()
self.check_node(node, 'lanscan') self.check_node(node, 'lanscan', True)
@unittest.skipUnless(os.name == 'posix', 'requires Posix') @unittest.skipUnless(os.name == 'posix', 'requires Posix')
def test_netstat_getnode(self): def test_netstat_getnode(self):
node = self.uuid._netstat_getnode() node = self.uuid._netstat_getnode()
self.check_node(node, 'netstat') self.check_node(node, 'netstat', True)
@unittest.skipUnless(os.name == 'nt', 'requires Windows') @unittest.skipUnless(os.name == 'nt', 'requires Windows')
def test_ipconfig_getnode(self): def test_ipconfig_getnode(self):
node = self.uuid._ipconfig_getnode() 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('win32wnet'), 'requires win32wnet')
@unittest.skipUnless(importable('netbios'), 'requires netbios') @unittest.skipUnless(importable('netbios'), 'requires netbios')
def test_netbios_getnode(self): def test_netbios_getnode(self):
node = self.uuid._netbios_getnode() node = self.uuid._netbios_getnode()
self.check_node(node) self.check_node(node, network=True)
def test_random_getnode(self): def test_random_getnode(self):
node = self.uuid._random_getnode() node = self.uuid._random_getnode()
# The multicast bit, i.e. the least significant bit of first octet, # Least significant bit of first octet must be set.
# must be set for randomly generated MAC addresses. See RFC 4122, self.assertTrue(node & 0x010000000000, '%012x' % node)
# $4.1.6. self.check_node(node)
self.assertTrue(node & (1 << 40), '%012x' % node)
self.check_node(node, check_bit=False)
@unittest.skipUnless(os.name == 'posix', 'requires Posix') @unittest.skipUnless(os.name == 'posix', 'requires Posix')
def test_unix_getnode(self): def test_unix_getnode(self):
...@@ -584,17 +575,13 @@ eth0 Link encap:Ethernet HWaddr 12:34:56:78:90:ab ...@@ -584,17 +575,13 @@ eth0 Link encap:Ethernet HWaddr 12:34:56:78:90:ab
node = self.uuid._unix_getnode() node = self.uuid._unix_getnode()
except TypeError: except TypeError:
self.skipTest('requires uuid_generate_time') self.skipTest('requires uuid_generate_time')
# Since we don't know the provenance of the MAC address, don't check self.check_node(node, 'unix')
# whether it is locally or universally administered.
self.check_node(node, 'unix', check_bit=False)
@unittest.skipUnless(os.name == 'nt', 'requires Windows') @unittest.skipUnless(os.name == 'nt', 'requires Windows')
@unittest.skipUnless(importable('ctypes'), 'requires ctypes') @unittest.skipUnless(importable('ctypes'), 'requires ctypes')
def test_windll_getnode(self): def test_windll_getnode(self):
node = self.uuid._windll_getnode() node = self.uuid._windll_getnode()
# Since we don't know the provenance of the MAC address, don't check self.check_node(node)
# whether it is locally or universally administered.
self.check_node(node, check_bit=False)
class TestInternalsWithoutExtModule(BaseTestInternals, unittest.TestCase): class TestInternalsWithoutExtModule(BaseTestInternals, unittest.TestCase):
......
...@@ -342,29 +342,11 @@ def _popen(command, *args): ...@@ -342,29 +342,11 @@ def _popen(command, *args):
env=env) env=env)
return proc 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): def _find_mac(command, args, hw_identifiers, get_index):
first_local_mac = None
try: try:
proc = _popen(command, *args.split()) proc = _popen(command, *args.split())
if not proc: if not proc:
return None return
with proc: with proc:
for line in proc.stdout: for line in proc.stdout:
words = line.lower().rstrip().split() words = line.lower().rstrip().split()
...@@ -373,9 +355,8 @@ def _find_mac(command, args, hw_identifiers, get_index): ...@@ -373,9 +355,8 @@ def _find_mac(command, args, hw_identifiers, get_index):
try: try:
word = words[get_index(i)] word = words[get_index(i)]
mac = int(word.replace(b':', b''), 16) mac = int(word.replace(b':', b''), 16)
if _is_universal(mac): if mac:
return mac return mac
first_local_mac = first_local_mac or mac
except (ValueError, IndexError): except (ValueError, IndexError):
# Virtual interfaces, such as those provided by # Virtual interfaces, such as those provided by
# VPNs, do not have a colon-delimited MAC address # VPNs, do not have a colon-delimited MAC address
...@@ -385,7 +366,6 @@ def _find_mac(command, args, hw_identifiers, get_index): ...@@ -385,7 +366,6 @@ def _find_mac(command, args, hw_identifiers, get_index):
pass pass
except OSError: except OSError:
pass pass
return first_local_mac or None
def _ifconfig_getnode(): def _ifconfig_getnode():
"""Get the hardware address on Unix by running ifconfig.""" """Get the hardware address on Unix by running ifconfig."""
...@@ -395,7 +375,6 @@ def _ifconfig_getnode(): ...@@ -395,7 +375,6 @@ def _ifconfig_getnode():
mac = _find_mac('ifconfig', args, keywords, lambda i: i+1) mac = _find_mac('ifconfig', args, keywords, lambda i: i+1)
if mac: if mac:
return mac return mac
return None
def _ip_getnode(): def _ip_getnode():
"""Get the hardware address on Unix by running ip.""" """Get the hardware address on Unix by running ip."""
...@@ -403,7 +382,6 @@ def _ip_getnode(): ...@@ -403,7 +382,6 @@ def _ip_getnode():
mac = _find_mac('ip', 'link list', [b'link/ether'], lambda i: i+1) mac = _find_mac('ip', 'link list', [b'link/ether'], lambda i: i+1)
if mac: if mac:
return mac return mac
return None
def _arp_getnode(): def _arp_getnode():
"""Get the hardware address on Unix by running arp.""" """Get the hardware address on Unix by running arp."""
...@@ -426,10 +404,8 @@ def _arp_getnode(): ...@@ -426,10 +404,8 @@ def _arp_getnode():
# This works on Linux, FreeBSD and NetBSD # This works on Linux, FreeBSD and NetBSD
mac = _find_mac('arp', '-an', [os.fsencode('(%s)' % ip_addr)], mac = _find_mac('arp', '-an', [os.fsencode('(%s)' % ip_addr)],
lambda i: i+2) lambda i: i+2)
# Return None instead of 0.
if mac: if mac:
return mac return mac
return None
def _lanscan_getnode(): def _lanscan_getnode():
"""Get the hardware address on Unix by running lanscan.""" """Get the hardware address on Unix by running lanscan."""
...@@ -439,36 +415,32 @@ def _lanscan_getnode(): ...@@ -439,36 +415,32 @@ def _lanscan_getnode():
def _netstat_getnode(): def _netstat_getnode():
"""Get the hardware address on Unix by running netstat.""" """Get the hardware address on Unix by running netstat."""
# This might work on AIX, Tru64 UNIX. # This might work on AIX, Tru64 UNIX.
first_local_mac = None
try: try:
proc = _popen('netstat', '-ia') proc = _popen('netstat', '-ia')
if not proc: if not proc:
return None return
with proc: with proc:
words = proc.stdout.readline().rstrip().split() words = proc.stdout.readline().rstrip().split()
try: try:
i = words.index(b'Address') i = words.index(b'Address')
except ValueError: except ValueError:
return None return
for line in proc.stdout: for line in proc.stdout:
try: try:
words = line.rstrip().split() words = line.rstrip().split()
word = words[i] word = words[i]
if len(word) == 17 and word.count(b':') == 5: if len(word) == 17 and word.count(b':') == 5:
mac = int(word.replace(b':', b''), 16) mac = int(word.replace(b':', b''), 16)
if _is_universal(mac): if mac:
return mac return mac
first_local_mac = first_local_mac or mac
except (ValueError, IndexError): except (ValueError, IndexError):
pass pass
except OSError: except OSError:
pass pass
return first_local_mac or None
def _ipconfig_getnode(): def _ipconfig_getnode():
"""Get the hardware address on Windows by running ipconfig.exe.""" """Get the hardware address on Windows by running ipconfig.exe."""
import os, re import os, re
first_local_mac = None
dirs = ['', r'c:\windows\system32', r'c:\winnt\system32'] dirs = ['', r'c:\windows\system32', r'c:\winnt\system32']
try: try:
import ctypes import ctypes
...@@ -486,23 +458,18 @@ def _ipconfig_getnode(): ...@@ -486,23 +458,18 @@ def _ipconfig_getnode():
for line in pipe: for line in pipe:
value = line.split(':')[-1].strip().lower() value = line.split(':')[-1].strip().lower()
if re.match('([0-9a-f][0-9a-f]-){5}[0-9a-f][0-9a-f]', value): if re.match('([0-9a-f][0-9a-f]-){5}[0-9a-f][0-9a-f]', value):
mac = int(value.replace('-', ''), 16) return int(value.replace('-', ''), 16)
if _is_universal(mac):
return mac
first_local_mac = first_local_mac or mac
return first_local_mac or None
def _netbios_getnode(): def _netbios_getnode():
"""Get the hardware address on Windows using NetBIOS calls. """Get the hardware address on Windows using NetBIOS calls.
See http://support.microsoft.com/kb/118623 for details.""" See http://support.microsoft.com/kb/118623 for details."""
import win32wnet, netbios import win32wnet, netbios
first_local_mac = None
ncb = netbios.NCB() ncb = netbios.NCB()
ncb.Command = netbios.NCBENUM ncb.Command = netbios.NCBENUM
ncb.Buffer = adapters = netbios.LANA_ENUM() ncb.Buffer = adapters = netbios.LANA_ENUM()
adapters._pack() adapters._pack()
if win32wnet.Netbios(ncb) != 0: if win32wnet.Netbios(ncb) != 0:
return None return
adapters._unpack() adapters._unpack()
for i in range(adapters.length): for i in range(adapters.length):
ncb.Reset() ncb.Reset()
...@@ -521,11 +488,7 @@ def _netbios_getnode(): ...@@ -521,11 +488,7 @@ def _netbios_getnode():
bytes = status.adapter_address[:6] bytes = status.adapter_address[:6]
if len(bytes) != 6: if len(bytes) != 6:
continue continue
mac = int.from_bytes(bytes, 'big') return 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
_generate_time_safe = _UuidCreate = None _generate_time_safe = _UuidCreate = None
...@@ -638,19 +601,9 @@ def _windll_getnode(): ...@@ -638,19 +601,9 @@ def _windll_getnode():
return UUID(bytes=bytes_(_buffer.raw)).node return UUID(bytes=bytes_(_buffer.raw)).node
def _random_getnode(): def _random_getnode():
"""Get a random node ID.""" """Get a random node ID, with eighth bit set as suggested by RFC 4122."""
# 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
import random import random
return random.getrandbits(48) | (1 << 40) return random.getrandbits(48) | 0x010000000000
_node = None _node = None
...@@ -673,14 +626,13 @@ def getnode(): ...@@ -673,14 +626,13 @@ def getnode():
getters = [_unix_getnode, _ifconfig_getnode, _ip_getnode, getters = [_unix_getnode, _ifconfig_getnode, _ip_getnode,
_arp_getnode, _lanscan_getnode, _netstat_getnode] _arp_getnode, _lanscan_getnode, _netstat_getnode]
for getter in getters: for getter in getters + [_random_getnode]:
try: try:
_node = getter() _node = getter()
except: except:
continue continue
if _node is not None: if _node is not None:
return _node return _node
return _random_getnode()
_last_timestamp = None _last_timestamp = None
......
Improve the private ``*_getnode()`` methods for UUID1 such that universally
administered MAC addresses are preferred over locally administered MAC
addresses. If only the latter is available, the first such one is returned.
Improve the related tests and fix some bugs there as well.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment