diff --git a/ironic_python_agent/netutils.py b/ironic_python_agent/netutils.py index 8f401855b..9eca69336 100644 --- a/ironic_python_agent/netutils.py +++ b/ironic_python_agent/netutils.py @@ -34,6 +34,13 @@ LLDP_ETHERTYPE = 0x88cc IFF_PROMISC = 0x100 SIOCGIFFLAGS = 0x8913 SIOCSIFFLAGS = 0x8914 +# SIOCETHTOOL from linux/sockios.h +SIOCETHTOOL = 0x8946 +# ETHTOOL_GPERMADDR from linux/ethtool.h +ETHTOOL_GPERMADDR = 0x00000020 +# MAX_ADDR_LEN from linux/netdevice.h +MAX_ADDR_LEN = 32 + INFINIBAND_ADDR_LEN = 59 # LLDP definitions needed to extract vlan information @@ -45,10 +52,25 @@ dot1_VLAN_NAME = "03" VLAN_ID_LEN = len(LLDP_802dot1_OUI + dot1_VLAN_NAME) +class ethtoolPermAddr(ctypes.Structure): + """Class for getting interface permanent MAC address""" + _fields_ = [("cmd", ctypes.c_uint32), + ("size", ctypes.c_uint32), + ("data", ctypes.c_uint8 * MAX_ADDR_LEN)] + + +class ifreq_data(ctypes.Union): + _fields_ = [("ifr_flags", ctypes.c_short), + ( + "ifr_data_ethtool_perm_addr", + ctypes.POINTER(ethtoolPermAddr))] + + class ifreq(ctypes.Structure): - """Class for setting flags on a socket.""" + """Class for ioctl on socket.""" + _anonymous_ = ("ifr_data",) _fields_ = [("ifr_ifrn", ctypes.c_char * 16), - ("ifr_flags", ctypes.c_short)] + ("ifr_data", ifreq_data)] class RawPromiscuousSockets(object): @@ -236,6 +258,23 @@ def get_ipv6_addr(interface_id): def get_mac_addr(interface_id): + """Retrieve permanent mac address, if unable to fallback to default one""" + try: + data = ethtoolPermAddr(cmd=ETHTOOL_GPERMADDR, size=MAX_ADDR_LEN) + ifr = ifreq(ifr_ifrn=interface_id.encode()) + ifr.ifr_data_ethtool_perm_addr = ctypes.pointer(data) + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 0) + fcntl.ioctl(sock.fileno(), SIOCETHTOOL, ifr) + # if not full of zeros + if any(data.data[:data.size]): + # kernel updates size to actual address size during ioctl call + permaddr = [f'{b:02x}' for b in data.data[:data.size]] + return ':'.join(permaddr) + except OSError: + pass + LOG.warning("Failed to get permanent mac address for interface %s, " + "falling back to default mac address", + interface_id) return get_default_ip_addr(socket.AF_PACKET, interface_id) diff --git a/ironic_python_agent/tests/unit/test_hardware.py b/ironic_python_agent/tests/unit/test_hardware.py index db76de250..6cb38bf6a 100644 --- a/ironic_python_agent/tests/unit/test_hardware.py +++ b/ironic_python_agent/tests/unit/test_hardware.py @@ -6279,6 +6279,7 @@ class TestCollectSystemLogs(base.IronicAgentTest): FakeAddr = namedtuple('FakeAddr', ('family', 'address')) +@mock.patch.object(netutils, 'get_mac_addr', autospec=True) @mock.patch.object(hardware.GenericHardwareManager, '_get_system_lshw_dict', autospec=True, return_value={'id': 'host'}) @mock.patch.object(hardware, 'get_managers', autospec=True, @@ -6303,7 +6304,8 @@ class TestListNetworkInterfaces(base.IronicAgentTest): mocked_listdir, mocked_net_if_addrs, mockedget_managers, - mocked_lshw): + mocked_lshw, + mocked_get_mac_addr): mocked_lshw.return_value = json.loads(hws.LSHW_JSON_OUTPUT_V2[0]) mocked_listdir.return_value = ['lo', 'eth0', 'foobar'] mocked_exists.side_effect = [False, False, True, True] @@ -6327,6 +6329,10 @@ class TestListNetworkInterfaces(base.IronicAgentTest): FakeAddr(socket.AF_INET6, 'fd00:1000::101') ] } + mocked_get_mac_addr.side_effect = lambda iface: { + 'lo': '00:00:00:00:00:00', + 'eth0': '00:0c:29:8c:11:b1', + }.get(iface) mocked_execute.return_value = ('em0\n', '') mock_has_carrier.return_value = True interfaces = self.hardware.list_network_interfaces() @@ -6348,7 +6354,8 @@ class TestListNetworkInterfaces(base.IronicAgentTest): mocked_listdir, mocked_net_if_addrs, mockedget_managers, - mocked_lshw): + mocked_lshw, + mocked_get_mac_addr): mocked_listdir.return_value = ['lo', 'eth0'] mocked_exists.side_effect = [False, False, True] mocked_open.return_value.__enter__ = lambda s: s @@ -6367,6 +6374,10 @@ class TestListNetworkInterfaces(base.IronicAgentTest): FakeAddr(socket.AF_PACKET, '00:0c:29:8c:11:b1') ] } + mocked_get_mac_addr.side_effect = lambda iface: { + 'lo': '00:00:00:00:00:00', + 'eth0': '00:0c:29:8c:11:b1', + }.get(iface) mocked_execute.return_value = ('em0\n', '') mock_has_carrier.return_value = True interfaces = self.hardware.list_network_interfaces() @@ -6390,7 +6401,8 @@ class TestListNetworkInterfaces(base.IronicAgentTest): mocked_listdir, mocked_net_if_addrs, mockedget_managers, - mocked_lshw): + mocked_lshw, + mocked_get_mac_addr): CONF.set_override('collect_lldp', True) mocked_listdir.return_value = ['lo', 'eth0'] mocked_exists.side_effect = [False, False, True] @@ -6410,6 +6422,10 @@ class TestListNetworkInterfaces(base.IronicAgentTest): FakeAddr(socket.AF_PACKET, '00:0c:29:8c:11:b1') ] } + mocked_get_mac_addr.side_effect = lambda iface: { + 'lo': '00:00:00:00:00:00', + 'eth0': '00:0c:29:8c:11:b1', + }.get(iface) mocked_lldp_info.return_value = {'eth0': [ (0, b''), (1, b'\x04\x88Z\x92\xecTY'), @@ -6444,7 +6460,8 @@ class TestListNetworkInterfaces(base.IronicAgentTest): mocked_listdir, mocked_net_if_addrs, mockedget_managers, - mocked_lshw): + mocked_lshw, + mocked_get_mac_addr): CONF.set_override('collect_lldp', True) mocked_listdir.return_value = ['lo', 'eth0'] mocked_exists.side_effect = [False, False, True] @@ -6464,6 +6481,10 @@ class TestListNetworkInterfaces(base.IronicAgentTest): FakeAddr(socket.AF_PACKET, '00:0c:29:8c:11:b1') ] } + mocked_get_mac_addr.side_effect = lambda iface: { + 'lo': '00:00:00:00:00:00', + 'eth0': '00:0c:29:8c:11:b1', + }.get(iface) mocked_lldp_info.side_effect = Exception('Boom!') mocked_execute.return_value = ('em0\n', '') mock_has_carrier.return_value = True @@ -6485,7 +6506,8 @@ class TestListNetworkInterfaces(base.IronicAgentTest): mocked_listdir, mocked_net_if_addrs, mockedget_managers, - mocked_lshw): + mocked_lshw, + mocked_get_mac_addr): mockedget_managers.return_value = [hardware.GenericHardwareManager()] mocked_listdir.return_value = ['lo', 'eth0'] @@ -6506,6 +6528,10 @@ class TestListNetworkInterfaces(base.IronicAgentTest): FakeAddr(socket.AF_PACKET, '00:0c:29:8c:11:b1') ] } + mocked_get_mac_addr.side_effect = lambda iface: { + 'lo': '00:00:00:00:00:00', + 'eth0': '00:0c:29:8c:11:b1', + }.get(iface) mocked_execute.return_value = ('em0\n', '') mock_has_carrier.return_value = False interfaces = self.hardware.list_network_interfaces() @@ -6526,7 +6552,8 @@ class TestListNetworkInterfaces(base.IronicAgentTest): mocked_listdir, mocked_net_if_addrs, mockedget_managers, - mocked_lshw): + mocked_lshw, + mocked_get_mac_addr): mocked_listdir.return_value = ['lo', 'eth0'] mocked_exists.side_effect = [False, False, True] mocked_open.return_value.__enter__ = lambda s: s @@ -6546,6 +6573,10 @@ class TestListNetworkInterfaces(base.IronicAgentTest): FakeAddr(socket.AF_PACKET, '00:0c:29:8c:11:b1') ] } + mocked_get_mac_addr.side_effect = lambda iface: { + 'lo': '00:00:00:00:00:00', + 'eth0': '00:0c:29:8c:11:b1', + }.get(iface) mocked_execute.return_value = ('em0\n', '') mock_has_carrier.return_value = True interfaces = self.hardware.list_network_interfaces() @@ -6567,7 +6598,8 @@ class TestListNetworkInterfaces(base.IronicAgentTest): mocked_listdir, mocked_net_if_addrs, mockedget_managers, - mocked_lshw): + mocked_lshw, + mocked_get_mac_addr): mocked_listdir.return_value = ['lo', 'bond0'] mocked_exists.side_effect = [False, False, True] mocked_open.return_value.__enter__ = lambda s: s @@ -6586,6 +6618,10 @@ class TestListNetworkInterfaces(base.IronicAgentTest): FakeAddr(socket.AF_PACKET, '00:0c:29:8c:11:b1') ] } + mocked_get_mac_addr.side_effect = lambda iface: { + 'lo': '00:00:00:00:00:00', + 'bond0': '00:0c:29:8c:11:b1', + }.get(iface) mocked_execute.return_value = ('\n', '') mock_has_carrier.return_value = True interfaces = self.hardware.list_network_interfaces() @@ -6610,7 +6646,8 @@ class TestListNetworkInterfaces(base.IronicAgentTest): mocked_listdir, mocked_net_if_addrs, mockedget_managers, - mocked_lshw): + mocked_lshw, + mocked_get_mac_addr): mocked_listdir.return_value = ['lo', 'eth0'] mocked_exists.side_effect = [False, False, True] mocked_open.return_value.__enter__ = lambda s: s @@ -6629,6 +6666,10 @@ class TestListNetworkInterfaces(base.IronicAgentTest): FakeAddr(socket.AF_PACKET, '00:0c:29:8c:11:b1') ] } + mocked_get_mac_addr.side_effect = lambda iface: { + 'lo': '00:00:00:00:00:00', + 'eth0': '00:0c:29:8c:11:b1', + }.get(iface) mocked_execute.return_value = ('em0\n', '') mock_has_carrier.return_value = True mock_get_pci.return_value = '0000:02:00.0' @@ -6654,7 +6695,8 @@ class TestListNetworkInterfaces(base.IronicAgentTest): mocked_listdir, mocked_net_if_addrs, mockedget_managers, - mocked_lshw): + mocked_lshw, + mocked_get_mac_addr): CONF.set_override('enable_vlan_interfaces', 'eth0.100') mocked_listdir.return_value = ['lo', 'eth0'] mocked_exists.side_effect = [False, False, True] @@ -6679,6 +6721,11 @@ class TestListNetworkInterfaces(base.IronicAgentTest): FakeAddr(socket.AF_PACKET, '00:0c:29:8c:11:b1') ] } + mocked_get_mac_addr.side_effect = lambda iface: { + 'lo': '00:00:00:00:00:00', + 'eth0': '00:0c:29:8c:11:b1', + 'eth0.100': '00:0c:29:8c:11:b1', + }.get(iface) mocked_execute.return_value = ('em0\n', '') mock_has_carrier.return_value = True interfaces = self.hardware.list_network_interfaces() @@ -6702,7 +6749,8 @@ class TestListNetworkInterfaces(base.IronicAgentTest): mocked_listdir, mocked_net_if_addrs, mockedget_managers, - mocked_lshw): + mocked_lshw, + mocked_get_mac_addr): CONF.set_override('collect_lldp', True) CONF.set_override('enable_vlan_interfaces', 'eth0') mocked_listdir.return_value = ['lo', 'eth0'] @@ -6734,6 +6782,12 @@ class TestListNetworkInterfaces(base.IronicAgentTest): FakeAddr(socket.AF_PACKET, '00:0c:29:8c:11:c2') ] } + mocked_get_mac_addr.side_effect = lambda iface: { + 'lo': '00:00:00:00:00:00', + 'eth0': '00:0c:29:8c:11:b1', + 'eth0.100': '00:0c:29:8c:11:c1', + 'eth0.101': '00:0c:29:8c:11:c2', + }.get(iface) mocked_lldp_info.return_value = {'eth0': [ (0, b''), (127, b'\x00\x80\xc2\x03\x00d\x08vlan-100'), @@ -6767,7 +6821,8 @@ class TestListNetworkInterfaces(base.IronicAgentTest): mocked_listdir, mocked_net_if_addrs, mockedget_managers, - mocked_lshw): + mocked_lshw, + mocked_get_mac_addr): CONF.set_override('collect_lldp', True) CONF.set_override('enable_vlan_interfaces', 'enp0s1') mocked_listdir.return_value = ['lo', 'eth0'] @@ -6805,7 +6860,8 @@ class TestListNetworkInterfaces(base.IronicAgentTest): mocked_listdir, mocked_net_if_addrs, mockedget_managers, - mocked_lshw): + mocked_lshw, + mocked_get_mac_addr): CONF.set_override('collect_lldp', True) CONF.set_override('enable_vlan_interfaces', 'all') mocked_listdir.return_value = ['lo', 'eth0', 'eth1'] diff --git a/releasenotes/notes/fix-mac-permaddr-0bc7d688eee4b814.yaml b/releasenotes/notes/fix-mac-permaddr-0bc7d688eee4b814.yaml new file mode 100644 index 000000000..7edb156f0 --- /dev/null +++ b/releasenotes/notes/fix-mac-permaddr-0bc7d688eee4b814.yaml @@ -0,0 +1,9 @@ +--- +fixes: + - | + Fixes IPA collecting the effective MAC address of NICs instead of the + pesistent MAC address. In case it fails to fetch the persistent address + falls back to effective MAC address. + See https://bugs.launchpad.net/ironic-python-agent/+bug/2103450 for + details. +