-
-
Notifications
You must be signed in to change notification settings - Fork 34.5k
bpo-28009: Fix uuid.uuid1() and uuid.get_node() on AIX #8672
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 38 commits
4ed41e9
ba47933
c6029a4
7e1874d
6a5bccb
a98309b
53ef750
6fd5f8e
3bb1c08
1af8e3d
d3eaab9
3469a01
399e8ec
6e2a9bf
bb3a460
db6767f
25d3ef1
fa9b43b
4a0c8f0
6463707
a0ef760
095e221
ec4c0e8
70a45f0
b1b4952
8f0687a
4756670
c55714a
10f272e
27b6c32
33969b9
7a57734
4628cea
d2830a2
0688727
ff1ee20
083e9c6
30cd017
27a972b
543e66d
588fda7
28f7a01
6fc2129
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -58,6 +58,7 @@ | |
| _DARWIN = platform.system() == 'Darwin' | ||
| _LINUX = platform.system() == 'Linux' | ||
| _WINDOWS = platform.system() == 'Windows' | ||
| _MAC_DELIM = b':' if not _AIX else b'.' | ||
|
taleinat marked this conversation as resolved.
Outdated
|
||
|
|
||
| RESERVED_NCS, RFC_4122, RESERVED_MICROSOFT, RESERVED_FUTURE = [ | ||
| 'reserved for NCS compatibility', 'specified in RFC 4122', | ||
|
|
@@ -347,24 +348,33 @@ def version(self): | |
| if self.variant == RFC_4122: | ||
| return int((self.int >> 76) & 0xf) | ||
|
|
||
| def _popen(command, *args): | ||
| import os, shutil, subprocess | ||
| executable = shutil.which(command) | ||
| if executable is None: | ||
| path = os.pathsep.join(('/sbin', '/usr/sbin')) | ||
| executable = shutil.which(command, path=path) | ||
|
|
||
| def _get_command_stdout(command, *args): | ||
| import io, os, shutil, subprocess | ||
|
|
||
| try: | ||
| executable = shutil.which(command) | ||
| if executable is None: | ||
| path = os.pathsep.join(('/sbin', '/usr/sbin')) | ||
|
taleinat marked this conversation as resolved.
Outdated
|
||
| 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) | ||
| if not proc: | ||
| 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 | ||
| stdout, stderr = proc.communicate() | ||
| return io.BytesIO(stdout) | ||
| except (OSError, subprocess.SubprocessError): | ||
| return None | ||
|
|
||
|
|
||
| # 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) | ||
|
|
@@ -384,48 +394,96 @@ def _popen(command, *args): | |
| def _is_universal(mac): | ||
| return not (mac & (1 << 41)) | ||
|
|
||
| def _find_mac(command, args, hw_identifiers, get_index): | ||
|
|
||
| # In the next two fucnctions: | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think I understand the general approach here, and it makes sense. These two functions need a better explanation of what they do, though—the comments here aren't enough for a reader to understand how they're intended to work, and the argument names Here's an attempt at some better names and a docstring: Given that Another concern I have is that the logic about what looks like a MAC address is mixed in with the logic about where to find it. It would be ideal to factor it out, like this: Then: etc.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks. |
||
| # command: name of command to run | ||
| # args: arguments passed to command | ||
| # hw_identifers: keywords used to locate a value | ||
| # f_index: lambda function to modify, if needed, an index value | ||
| # keyword and value are on the same line aka 'inline' | ||
| def _find_mac_inline(command, args, hw_identifiers, f_index): | ||
| stdout = _get_command_stdout(command, args) | ||
| if stdout is None: | ||
| return None | ||
|
|
||
| first_local_mac = None | ||
| for line in stdout: | ||
| words = line.lower().rstrip().split() | ||
| for i in range(len(words)): | ||
| if words[i] in hw_identifiers: | ||
| try: | ||
| word = words[f_index(i)] | ||
| mac = int(word.replace(_MAC_DELIM, b''), 16) | ||
| 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 | ||
|
|
||
|
|
||
| # Keyword is only on firstline - values on remaining lines | ||
| def _find_mac_nextlines(command, args, hw_identifiers, f_index): | ||
| stdout = _get_command_stdout(command, args) | ||
| if stdout is None: | ||
| return None | ||
|
|
||
| keywords = stdout.readline().rstrip().split() | ||
| try: | ||
| proc = _popen(command, *args.split()) | ||
| if not proc: | ||
| return None | ||
| with proc: | ||
| for line in proc.stdout: | ||
| words = line.lower().rstrip().split() | ||
| for i in range(len(words)): | ||
| if words[i] in hw_identifiers: | ||
| try: | ||
| word = words[get_index(i)] | ||
| mac = int(word.replace(b':', b''), 16) | ||
| if _is_universal(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 | ||
| # as expected, but a 16-byte HWAddr separated by | ||
| # dashes. These should be ignored in favor of a | ||
| # real MAC address | ||
| pass | ||
| except OSError: | ||
| pass | ||
| i = keywords.index(hw_identifiers) | ||
| except ValueError: | ||
| return None | ||
| # we have the index (i) into the data that follows | ||
|
|
||
| first_local_mac = None | ||
| for line in stdout: | ||
| try: | ||
| words = line.rstrip().split() | ||
| word = words[f_index(i)] | ||
| # (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) | ||
| 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 | ||
|
|
||
|
|
||
| # The following functions call external programs to 'get' a macaddr value to | ||
| # be used as basis for an uuid | ||
| def _ifconfig_getnode(): | ||
| """Get the hardware address on Unix by running ifconfig.""" | ||
| # This works on Linux ('' or '-a'), Tru64 ('-av'), but not all Unixes. | ||
| keywords = (b'hwaddr', b'ether', b'address:', b'lladdr') | ||
| for args in ('', '-a', '-av'): | ||
| mac = _find_mac('ifconfig', args, keywords, lambda i: i+1) | ||
| mac = _find_mac_inline('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.""" | ||
| # This works on Linux with iproute2. | ||
| mac = _find_mac('ip', 'link', [b'link/ether'], lambda i: i+1) | ||
| mac = _find_mac_inline('ip', 'link', [b'link/ether'], lambda i: i+1) | ||
| if mac: | ||
| return mac | ||
| return None | ||
|
|
@@ -439,17 +497,17 @@ def _arp_getnode(): | |
| return None | ||
|
|
||
| # 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) | ||
| mac = _find_mac_inline('arp', '-an', [os.fsencode(ip_addr)], lambda i: -1) | ||
| if mac: | ||
| return mac | ||
|
|
||
| # This works on OpenBSD | ||
| mac = _find_mac('arp', '-an', [os.fsencode(ip_addr)], lambda i: i+1) | ||
| mac = _find_mac_inline('arp', '-an', [os.fsencode(ip_addr)], lambda i: i+1) | ||
| if mac: | ||
| return mac | ||
|
|
||
| # This works on Linux, FreeBSD and NetBSD | ||
| mac = _find_mac('arp', '-an', [os.fsencode('(%s)' % ip_addr)], | ||
| mac = _find_mac_inline('arp', '-an', [os.fsencode('(%s)' % ip_addr)], | ||
| lambda i: i+2) | ||
| # Return None instead of 0. | ||
| if mac: | ||
|
|
@@ -459,36 +517,12 @@ def _arp_getnode(): | |
| def _lanscan_getnode(): | ||
| """Get the hardware address on Unix by running lanscan.""" | ||
| # This might work on HP-UX. | ||
| return _find_mac('lanscan', '-ai', [b'lan0'], lambda i: 0) | ||
| return _find_mac_inline('lanscan', '-ai', [b'lan0'], lambda i: 0) | ||
|
|
||
| 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 | ||
| with proc: | ||
| words = proc.stdout.readline().rstrip().split() | ||
| try: | ||
| i = words.index(b'Address') | ||
| except ValueError: | ||
| return None | ||
| 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): | ||
| return mac | ||
| first_local_mac = first_local_mac or mac | ||
| except (ValueError, IndexError): | ||
| pass | ||
| except OSError: | ||
| pass | ||
| return first_local_mac or None | ||
| # This works on AIX and might work on Tru64 UNIX. | ||
| return _find_mac_nextlines('netstat', '-ia', b'Address', lambda i: i) | ||
|
|
||
| def _ipconfig_getnode(): | ||
| """Get the hardware address on Windows by running ipconfig.exe.""" | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| Fix uuid.getnode() on AIX to properly parse MAC addresses in netstat's output. | ||
| Patch by Michael Felt. |
Uh oh!
There was an error while loading. Please reload this page.