Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions ironic_python_agent/hardware.py
Original file line number Diff line number Diff line change
Expand Up @@ -810,11 +810,13 @@ class NetworkInterface(encoding.SerializableComparable):
serializable_fields = ('name', 'mac_address', 'ipv4_address',
'ipv6_address', 'has_carrier', 'lldp',
'vendor', 'product', 'client_id',
'biosdevname', 'speed_mbps')
'biosdevname', 'speed_mbps', 'pci_address',
'driver')

def __init__(self, name, mac_addr, ipv4_address=None, ipv6_address=None,
has_carrier=True, lldp=None, vendor=None, product=None,
client_id=None, biosdevname=None, speed_mbps=None):
client_id=None, biosdevname=None, speed_mbps=None,
pci_address=None, driver=None):
self.name = name
self.mac_address = mac_addr
self.ipv4_address = ipv4_address
Expand All @@ -825,6 +827,8 @@ def __init__(self, name, mac_addr, ipv4_address=None, ipv6_address=None,
self.product = product
self.biosdevname = biosdevname
self.speed_mbps = speed_mbps
self.pci_address = pci_address
self.driver = driver
# client_id is used for InfiniBand only. we calculate the DHCP
# client identifier Option to allow DHCP to work over InfiniBand.
# see https://tools.ietf.org/html/rfc4390
Expand Down Expand Up @@ -1448,7 +1452,10 @@ def get_interface_info(self, interface_name):
vendor=_get_device_info(interface_name, 'net', 'vendor'),
product=_get_device_info(interface_name, 'net', 'device'),
biosdevname=self.get_bios_given_nic_name(interface_name),
speed_mbps=self._get_network_speed(interface_name))
speed_mbps=self._get_network_speed(interface_name),
pci_address=netutils.get_interface_pci_address(interface_name),
driver=netutils.get_interface_driver(interface_name)
)

def get_ipv4_addr(self, interface_id):
return netutils.get_ipv4_addr(interface_id)
Expand Down
87 changes: 71 additions & 16 deletions ironic_python_agent/netutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@
import struct
import sys

import netifaces
from oslo_config import cfg
from oslo_log import log as logging
from oslo_utils import netutils
import psutil

from ironic_python_agent import utils

Expand All @@ -34,6 +34,13 @@
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
Expand All @@ -45,10 +52,25 @@
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):
Expand Down Expand Up @@ -214,31 +236,46 @@ def _get_lldp_info(interfaces):
return lldp_info


def get_default_ip_addr(type, interface_id):
"""Retrieve default IPv4 or IPv6 address."""
def get_default_ip_addr(family, interface_id):
"""Retrieve default IPv4, IPv6 or mac address."""
try:
addrs = netifaces.ifaddresses(interface_id)
return addrs[type][0]['addr']
except (ValueError, IndexError, KeyError):
addrs = psutil.net_if_addrs()[interface_id]
for addr in addrs:
if addr.family == family:
return addr.address
except KeyError:
# No default IP address found
return None
pass
return None


def get_ipv4_addr(interface_id):
return get_default_ip_addr(netifaces.AF_INET, interface_id)
return get_default_ip_addr(socket.AF_INET, interface_id)


def get_ipv6_addr(interface_id):
return get_default_ip_addr(netifaces.AF_INET6, interface_id)
return get_default_ip_addr(socket.AF_INET6, interface_id)


def get_mac_addr(interface_id):
"""Retrieve permanent mac address, if unable to fallback to default one"""
try:
addrs = netifaces.ifaddresses(interface_id)
return addrs[netifaces.AF_LINK][0]['addr']
except (ValueError, IndexError, KeyError):
# No mac address found
return None
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)


# Other options...
Expand Down Expand Up @@ -282,6 +319,24 @@ def interface_has_carrier(interface_name):
return False


def get_interface_pci_address(interface_name):
path = '/sys/class/net/{}/device'.format(interface_name)
try:
return os.path.basename(os.readlink(path))
except FileNotFoundError:
LOG.debug('No bus address found for interface %s',
interface_name)


def get_interface_driver(interface_name):
path = '/sys/class/net/{}/device/driver'.format(interface_name)
try:
return os.path.basename(os.readlink(path))
except FileNotFoundError:
LOG.debug('No driver found for interface %s',
interface_name)


def wrap_ipv6(ip):
if netutils.is_valid_ipv6(ip):
return "[%s]" % ip
Expand Down
Loading