Blob Blame History Raw
#
# Copyright (c) 2018-2020 Red Hat, Inc.
#
# This file is part of nmstate
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 2.1 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#

from libnmstate.ethtool import minimal_ethtool
from libnmstate.nm import sriov
from libnmstate.schema import Ethernet
from libnmstate.schema import Interface
from .common import NM


ZEROED_MAC = "00:00:00:00:00:00"


class WiredSetting:
    def __init__(self, state):
        self.mtu = state.get(Interface.MTU)
        self.mac = state.get(Interface.MAC)

        ethernet = state.get(Ethernet.CONFIG_SUBTREE, {})
        self.speed = ethernet.get(Ethernet.SPEED)
        self.duplex = ethernet.get(Ethernet.DUPLEX)
        self.auto_negotiation = ethernet.get(Ethernet.AUTO_NEGOTIATION)

    def __hash__(self):
        return hash(self.__key())

    def __eq__(self, other):
        return self is other or self.__key() == other.__key()

    def __ne__(self, other):
        return not self.__eq__(other)

    def __bool__(self):
        return bool(
            self.mac
            or self.mtu
            or self.speed
            or self.duplex
            or (self.auto_negotiation is not None)
        )

    def __key(self):
        return (
            self.mtu,
            self.mac,
            self.speed,
            self.duplex,
            self.auto_negotiation,
        )


def create_setting(iface_state, base_con_profile):
    setting = WiredSetting(iface_state)

    nm_wired_setting = None
    if base_con_profile:
        nm_wired_setting = base_con_profile.get_setting_wired()
        if nm_wired_setting:
            nm_wired_setting = nm_wired_setting.duplicate()

    if not setting:
        return nm_wired_setting

    if not nm_wired_setting:
        nm_wired_setting = NM.SettingWired.new()

    if setting.mac:
        nm_wired_setting.props.cloned_mac_address = setting.mac

    if setting.mtu:
        nm_wired_setting.props.mtu = setting.mtu

    if setting.auto_negotiation:
        nm_wired_setting.props.auto_negotiate = True
        if not setting.speed and not setting.duplex:
            nm_wired_setting.props.speed = 0
            nm_wired_setting.props.duplex = None

        elif not setting.speed:
            ethtool_results = minimal_ethtool(str(iface_state[Interface.NAME]))
            setting.speed = ethtool_results[Ethernet.SPEED]
        elif not setting.duplex:
            ethtool_results = minimal_ethtool(str(iface_state[Interface.NAME]))
            setting.duplex = ethtool_results[Ethernet.DUPLEX]

    elif setting.auto_negotiation is False:
        nm_wired_setting.props.auto_negotiate = False
        ethtool_results = minimal_ethtool(str(iface_state[Interface.NAME]))
        if not setting.speed:
            setting.speed = ethtool_results[Ethernet.SPEED]
        if not setting.duplex:
            setting.duplex = ethtool_results[Ethernet.DUPLEX]

    if setting.speed:
        nm_wired_setting.props.speed = setting.speed

    if setting.duplex in [Ethernet.HALF_DUPLEX, Ethernet.FULL_DUPLEX]:
        nm_wired_setting.props.duplex = setting.duplex

    return nm_wired_setting


def get_info(device):
    """
    Provides the current active values for a device
    """
    info = {}

    iface = device.get_iface()
    try:
        mtu = int(device.get_mtu())
        if mtu:
            info[Interface.MTU] = mtu
    except AttributeError:
        pass

    mac = device.get_hw_address()
    if not mac:
        mac = _get_mac_address_from_sysfs(iface)

    # A device may not have a MAC or it may not yet be "realized" (zeroed mac).
    if mac and mac != ZEROED_MAC:
        info[Interface.MAC] = mac

    if device.get_device_type() == NM.DeviceType.ETHERNET:
        ethernet = _get_ethernet_info(device, iface)
        if ethernet:
            info[Ethernet.CONFIG_SUBTREE] = ethernet

    return info


def _get_mac_address_from_sysfs(ifname):
    """
    Fetch the mac address of an interface from sysfs.
    This is a workaround for https://bugzilla.redhat.com/1786937.
    """
    mac = None
    sysfs_path = f"/sys/class/net/{ifname}/address"
    try:
        with open(sysfs_path) as f:
            mac = f.read().rstrip("\n").upper()
    except FileNotFoundError:
        pass
    return mac


def _get_ethernet_info(device, iface):
    ethernet = {}
    try:
        speed = int(device.get_speed())
        if speed > 0:
            ethernet[Ethernet.SPEED] = speed
        else:
            return None
    except AttributeError:
        return None

    ethtool_results = minimal_ethtool(iface)
    auto_setting = ethtool_results[Ethernet.AUTO_NEGOTIATION]
    if auto_setting is True:
        ethernet[Ethernet.AUTO_NEGOTIATION] = True
    elif auto_setting is False:
        ethernet[Ethernet.AUTO_NEGOTIATION] = False
    else:
        return None

    duplex_setting = ethtool_results[Ethernet.DUPLEX]
    if duplex_setting in [Ethernet.HALF_DUPLEX, Ethernet.FULL_DUPLEX]:
        ethernet[Ethernet.DUPLEX] = duplex_setting
    else:
        return None

    sriov_info = sriov.get_info(device)
    if sriov_info:
        ethernet.update(sriov_info)

    return ethernet