diff --git a/Lib/test/test_uuid.py b/Lib/test/test_uuid.py index 115e66c2160..bfa9f973d77 100644 --- a/Lib/test/test_uuid.py +++ b/Lib/test/test_uuid.py @@ -320,6 +320,24 @@ class TestUUID(unittest.TestCase): if node is not None: self.check_node(node, 'ifconfig') + @unittest.skipUnless(os.name == 'posix', 'requires Posix') + def test_arp_getnode(self): + node = uuid._arp_getnode() + if node is not None: + self.check_node(node, 'arp') + + @unittest.skipUnless(os.name == 'posix', 'requires Posix') + def test_lanscan_getnode(self): + node = uuid._lanscan_getnode() + if node is not None: + self.check_node(node, 'lanscan') + + @unittest.skipUnless(os.name == 'posix', 'requires Posix') + def test_netstat_getnode(self): + node = uuid._netstat_getnode() + if node is not None: + self.check_node(node, 'netstat') + @unittest.skipUnless(os.name == 'nt', 'requires Windows') def test_ipconfig_getnode(self): node = uuid._ipconfig_getnode() @@ -377,7 +395,7 @@ eth0 Link encap:Ethernet HWaddr 12:34:56:78:90:ab return_value=popen): mac = uuid._find_mac( command='ifconfig', - arg='', + args='', hw_identifiers=[b'hwaddr'], get_index=lambda x: x + 1, ) diff --git a/Lib/uuid.py b/Lib/uuid.py index 018bb6f51ba..598aea1956a 100644 --- a/Lib/uuid.py +++ b/Lib/uuid.py @@ -304,7 +304,7 @@ class UUID(object): if self.variant == RFC_4122: return int((self.int >> 76) & 0xf) -def _find_mac(command, arg, hw_identifiers, get_index): +def _popen(command, *args): import os, shutil, subprocess executable = shutil.which(command) if executable is None: @@ -312,28 +312,32 @@ def _find_mac(command, arg, hw_identifiers, get_index): executable = shutil.which(command, path=path) if executable is None: return None + # LC_ALL=C to ensure English output, stderr=DEVNULL to prevent output + # on stderr (Note: we don't have an example where the words we search + # for are actually localized, but in theory some system could do so.) + env = dict(os.environ) + env['LC_ALL'] = 'C' + proc = subprocess.Popen((executable,) + args, + stdout=subprocess.PIPE, + stderr=subprocess.DEVNULL, + env=env) + return proc +def _find_mac(command, args, hw_identifiers, get_index): try: - # LC_ALL=C to ensure English output, stderr=DEVNULL to prevent output - # on stderr (Note: we don't have an example where the words we search - # for are actually localized, but in theory some system could do so.) - env = dict(os.environ) - env['LC_ALL'] = 'C' - cmd = [executable] - if arg: - cmd.append(arg) - proc = subprocess.Popen(cmd, - stdout=subprocess.PIPE, - stderr=subprocess.DEVNULL, - env=env) + proc = _popen(command, *args.split()) + if not proc: + return with proc: for line in proc.stdout: - words = line.lower().split() + words = line.lower().rstrip().split() for i in range(len(words)): if words[i] in hw_identifiers: try: - return int( - words[get_index(i)].replace(b':', b''), 16) + word = words[get_index(i)] + mac = int(word.replace(b':', b''), 16) + if mac: + return mac except (ValueError, IndexError): # Virtual interfaces, such as those provided by # VPNs, do not have a colon-delimited MAC address @@ -346,28 +350,50 @@ def _find_mac(command, arg, hw_identifiers, get_index): def _ifconfig_getnode(): """Get the hardware address on Unix by running ifconfig.""" - import os - # This works on Linux ('' or '-a'), Tru64 ('-av'), but not all Unixes. for args in ('', '-a', '-av'): mac = _find_mac('ifconfig', args, [b'hwaddr', b'ether'], lambda i: i+1) if mac: return mac - import socket +def _arp_getnode(): + """Get the hardware address on Unix by running arp.""" + import os, socket ip_addr = socket.gethostbyname(socket.gethostname()) # Try getting the MAC addr from arp based on our IP address (Solaris). - mac = _find_mac('arp', '-an', [os.fsencode(ip_addr)], lambda i: -1) - if mac: - return mac + return _find_mac('arp', '-an', [os.fsencode(ip_addr)], lambda i: -1) +def _lanscan_getnode(): + """Get the hardware address on Unix by running lanscan.""" # This might work on HP-UX. - mac = _find_mac('lanscan', '-ai', [b'lan0'], lambda i: 0) - if mac: - return mac + return _find_mac('lanscan', '-ai', [b'lan0'], lambda i: 0) - return None +def _netstat_getnode(): + """Get the hardware address on Unix by running netstat.""" + # This might work on AIX, Tru64 UNIX and presumably on IRIX. + try: + proc = _popen('netstat', '-ia') + if not proc: + return + with proc: + words = proc.stdout.readline().rstrip().split() + try: + i = words.index(b'Address') + except ValueError: + 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 mac: + return mac + except (ValueError, IndexError): + pass + except OSError: + pass def _ipconfig_getnode(): """Get the hardware address on Windows by running ipconfig.exe.""" @@ -509,7 +535,8 @@ def getnode(): if sys.platform == 'win32': getters = [_windll_getnode, _netbios_getnode, _ipconfig_getnode] else: - getters = [_unixdll_getnode, _ifconfig_getnode] + getters = [_unixdll_getnode, _ifconfig_getnode, _arp_getnode, + _lanscan_getnode, _netstat_getnode] for getter in getters + [_random_getnode]: try: diff --git a/Misc/NEWS b/Misc/NEWS index a7efe99f4d1..da79643b965 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -183,6 +183,9 @@ Core and Builtins Library ------- +- Issue #17293: uuid.getnode() now determines MAC address on AIX using netstat. + Based on patch by Aivars Kalvāns. + - Issue #22769: Fixed ttk.Treeview.tag_has() when called without arguments. - Issue #22417: Verify certificates by default in httplib (PEP 476).