netutils: Use ethtool ioctl to get permanent mac address
Fetching the permanent MAC address of the interface instead of the default one allows to get the right one in case it got changed during setup (likely with a bonding setup). In order to fetch the permanent MAC address of a given interface, one can either use Netlink (either rtnetlink or ethtool), or use ethtool ioctl. The use of ioctl feels simpler and requires no additional dependency. The implementation falls back to older behavior should an error occur. Closes-Bug: #2103450 Change-Id: I54151990e396ddcf775128ca24d3db08e45c256d Signed-off-by: Nicolas Belouin <nicolas.belouin@suse.com>
This commit is contained in:
@@ -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)
|
||||
|
||||
|
||||
|
@@ -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']
|
||||
|
@@ -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.
|
||||
|
Reference in New Issue
Block a user