From bbf6898b90b78ea36d6f70e4af21ccd4cb1a2b46 Mon Sep 17 00:00:00 2001 From: Bence Romsics Date: Thu, 25 Jun 2020 16:36:49 +0200 Subject: [PATCH] New method in netutils: get_mac_addr_by_ipv6 This method is practically the reverse of get_ipv6_addr_by_EUI64(), so we can extract the MAC address from IPv6 addresses that were generated from interface identifiers. Change-Id: I48720d38649104f9f2f0a8fd208f2aac7548644e Related-Change: https://review.opendev.org/718729 --- oslo_utils/netutils.py | 29 ++++++++++++++++ oslo_utils/tests/test_netutils.py | 33 +++++++++++++++++++ ...get_mac_addr_by_ipv6-c3ce6a35a69f7c2b.yaml | 5 +++ 3 files changed, 67 insertions(+) create mode 100644 releasenotes/notes/netutils-get_mac_addr_by_ipv6-c3ce6a35a69f7c2b.yaml diff --git a/oslo_utils/netutils.py b/oslo_utils/netutils.py index 26e16dc7..af06a43e 100644 --- a/oslo_utils/netutils.py +++ b/oslo_utils/netutils.py @@ -193,6 +193,35 @@ def get_ipv6_addr_by_EUI64(prefix, mac): 'EUI-64: %s') % prefix) +def get_mac_addr_by_ipv6(ipv6, dialect=netaddr.mac_unix_expanded): + """Extract MAC address from interface identifier based IPv6 address. + + For example from link-local addresses (fe80::/10) generated from MAC. + + :param ipv6: An interface identifier (i.e. mostly MAC) based IPv6 + address as a netaddr.IPAddress() object. + :param dialect: The netaddr dialect of the the object returned. + Defaults to netaddr.mac_unix_expanded. + :returns: A MAC address as a netaddr.EUI() object. + + See also: + * https://tools.ietf.org/html/rfc4291#appendix-A + * https://tools.ietf.org/html/rfc4291#section-2.5.6 + + .. versionadded:: 4.3.0 + """ + return netaddr.EUI(int( + # out of the lowest 8 bytes (byte positions 8-1) + # delete the middle 2 bytes (5-4, 0xff_fe) + # by shifting the highest 3 bytes to the right by 2 bytes (8-6 -> 6-4) + (((ipv6 & 0xff_ff_ff_00_00_00_00_00) >> 16) + + # adding the lowest 3 bytes as they are (3-1) + (ipv6 & 0xff_ff_ff)) ^ + # then invert the universal/local bit + 0x02_00_00_00_00_00), + dialect=dialect) + + def is_ipv6_enabled(): """Check if IPv6 support is enabled on the platform. diff --git a/oslo_utils/tests/test_netutils.py b/oslo_utils/tests/test_netutils.py index 31d9c752..260e1b12 100644 --- a/oslo_utils/tests/test_netutils.py +++ b/oslo_utils/tests/test_netutils.py @@ -17,6 +17,7 @@ import contextlib import socket from unittest import mock +import netaddr import netifaces from oslotest import base as test_base import six @@ -357,6 +358,38 @@ class IPv6byEUI64TestCase(test_base.BaseTestCase): netutils.get_ipv6_addr_by_EUI64(prefix, mac)) +class MACbyIPv6TestCase(test_base.BaseTestCase): + """Unit tests to extract MAC from IPv6.""" + + def test_reverse_generate_IPv6_by_EUI64(self): + self.assertEqual( + netaddr.EUI('00:16:3e:33:44:55'), + netutils.get_mac_addr_by_ipv6( + netaddr.IPAddress('2001:db8::216:3eff:fe33:4455')), + ) + + def test_random_qemu_mac(self): + self.assertEqual( + netaddr.EUI('52:54:00:42:02:19'), + netutils.get_mac_addr_by_ipv6( + netaddr.IPAddress('fe80::5054:ff:fe42:219')), + ) + + def test_local(self): + self.assertEqual( + netaddr.EUI('02:00:00:00:00:00'), + netutils.get_mac_addr_by_ipv6( + netaddr.IPAddress('fe80::ff:fe00:0')), + ) + + def test_universal(self): + self.assertEqual( + netaddr.EUI('00:00:00:00:00:00'), + netutils.get_mac_addr_by_ipv6( + netaddr.IPAddress('fe80::200:ff:fe00:0')), + ) + + @contextlib.contextmanager def mock_file_content(content): # Allows StringIO to act like a context manager-enabled file. diff --git a/releasenotes/notes/netutils-get_mac_addr_by_ipv6-c3ce6a35a69f7c2b.yaml b/releasenotes/notes/netutils-get_mac_addr_by_ipv6-c3ce6a35a69f7c2b.yaml new file mode 100644 index 00000000..396ed99f --- /dev/null +++ b/releasenotes/notes/netutils-get_mac_addr_by_ipv6-c3ce6a35a69f7c2b.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + New method ``netutils.get_mac_addr_by_ipv6(ipv6, dialect)`` extracts + the MAC address from IPv6 addresses generated from MACs.