Blame libnmstate/nm/bridge.py

Packit Service 0535c1
#
Packit Service 0535c1
# Copyright (c) 2018-2020 Red Hat, Inc.
Packit Service 0535c1
#
Packit Service 0535c1
# This file is part of nmstate
Packit Service 0535c1
#
Packit Service 0535c1
# This program is free software: you can redistribute it and/or modify
Packit Service 0535c1
# it under the terms of the GNU Lesser General Public License as published by
Packit Service 0535c1
# the Free Software Foundation, either version 2.1 of the License, or
Packit Service 0535c1
# (at your option) any later version.
Packit Service 0535c1
#
Packit Service 0535c1
# This program is distributed in the hope that it will be useful,
Packit Service 0535c1
# but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit Service 0535c1
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Packit Service 0535c1
# GNU Lesser General Public License for more details.
Packit Service 0535c1
#
Packit Service 0535c1
# You should have received a copy of the GNU Lesser General Public License
Packit Service 0535c1
# along with this program. If not, see <https://www.gnu.org/licenses/>.
Packit Service 0535c1
#
Packit Service 0535c1
Packit Service 0535c1
import glob
Packit Service 0535c1
import os
Packit Service 0535c1
Packit Service 0535c1
from libnmstate.nm import connection
Packit Service 0535c1
from libnmstate.nm.bridge_port_vlan import PortVlanFilter
Packit Service 0535c1
from libnmstate.schema import LinuxBridge as LB
Packit Service 0535c1
from .common import NM
Packit Service 0535c1
Packit Service 0535c1
Packit Service 0535c1
BRIDGE_TYPE = "bridge"
Packit Service 0535c1
Packit Service 0535c1
BRIDGE_PORT_NMSTATE_TO_SYSFS = {
Packit Service 0535c1
    LB.Port.STP_HAIRPIN_MODE: "hairpin_mode",
Packit Service 0535c1
    LB.Port.STP_PATH_COST: "path_cost",
Packit Service 0535c1
    LB.Port.STP_PRIORITY: "priority",
Packit Service 0535c1
}
Packit Service 0535c1
Packit Service 0535c1
SYSFS_USER_HZ_KEYS = [
Packit Service 0535c1
    "forward_delay",
Packit Service 0535c1
    "ageing_time",
Packit Service 0535c1
    "hello_time",
Packit Service 0535c1
    "max_age",
Packit Service 0535c1
]
Packit Service 0535c1
Packit Service 0535c1
OPT = LB.Options
Packit Service 0535c1
Packit Service 0535c1
EXTRA_OPTIONS_MAP = {
Packit Service 0535c1
    OPT.HELLO_TIMER: "hello_timer",
Packit Service 0535c1
    OPT.GC_TIMER: "gc_timer",
Packit Service 0535c1
    OPT.MULTICAST_ROUTER: "multicast_router",
Packit Service 0535c1
    OPT.GROUP_ADDR: "group_addr",
Packit Service 0535c1
    OPT.HASH_MAX: "hash_max",
Packit Service 0535c1
    OPT.MULTICAST_LAST_MEMBER_COUNT: "multicast_last_member_count",
Packit Service 0535c1
    OPT.MULTICAST_LAST_MEMBER_INTERVAL: "multicast_last_member_interval",
Packit Service 0535c1
    OPT.MULTICAST_QUERIER: "multicast_querier",
Packit Service 0535c1
    OPT.MULTICAST_QUERIER_INTERVAL: "multicast_querier_interval",
Packit Service 0535c1
    OPT.MULTICAST_QUERY_USE_IFADDR: "multicast_query_use_ifaddr",
Packit Service 0535c1
    OPT.MULTICAST_QUERY_INTERVAL: "multicast_query_interval",
Packit Service 0535c1
    OPT.MULTICAST_QUERY_RESPONSE_INTERVAL: "multicast_query_response_interval",
Packit Service 0535c1
    OPT.MULTICAST_STARTUP_QUERY_COUNT: "multicast_startup_query_count",
Packit Service 0535c1
    OPT.MULTICAST_STARTUP_QUERY_INTERVAL: "multicast_startup_query_interval",
Packit Service 0535c1
}
Packit Service 0535c1
Packit Service 0535c1
BOOL_OPTIONS = (OPT.MULTICAST_QUERIER, OPT.MULTICAST_QUERY_USE_IFADDR)
Packit Service 0535c1
Packit Service 0535c1
NM_BRIDGE_OPTIONS_MAP = {
Packit Service 0535c1
    OPT.GROUP_ADDR: "group_address",
Packit Service 0535c1
    OPT.HASH_MAX: "multicast_hash_max",
Packit Service 0535c1
    OPT.MULTICAST_LAST_MEMBER_COUNT: "multicast_last_member_count",
Packit Service 0535c1
    OPT.MULTICAST_LAST_MEMBER_INTERVAL: "multicast_last_member_interval",
Packit Service 0535c1
    OPT.MULTICAST_MEMBERSHIP_INTERVAL: "multicast_membership_interval",
Packit Service 0535c1
    OPT.MULTICAST_QUERIER: "multicast_querier",
Packit Service 0535c1
    OPT.MULTICAST_QUERIER_INTERVAL: "multicast_querier_interval",
Packit Service 0535c1
    OPT.MULTICAST_QUERY_USE_IFADDR: "multicast_query_use_ifaddr",
Packit Service 0535c1
    OPT.MULTICAST_QUERY_INTERVAL: "multicast_query_interval",
Packit Service 0535c1
    OPT.MULTICAST_QUERY_RESPONSE_INTERVAL: "multicast_query_response_interval",
Packit Service 0535c1
    OPT.MULTICAST_STARTUP_QUERY_COUNT: "multicast_startup_query_count",
Packit Service 0535c1
    OPT.MULTICAST_STARTUP_QUERY_INTERVAL: "multicast_startup_query_interval",
Packit Service 0535c1
}
Packit Service 0535c1
Packit Service 0535c1
Packit Service 0535c1
def create_setting(
Packit Service 0535c1
    bridge_state, base_con_profile, original_desired_iface_state
Packit Service 0535c1
):
Packit Service 0535c1
    options = original_desired_iface_state.get(LB.CONFIG_SUBTREE, {}).get(
Packit Service 0535c1
        LB.OPTIONS_SUBTREE
Packit Service 0535c1
    )
Packit Service 0535c1
    bridge_setting = _get_current_bridge_setting(base_con_profile)
Packit Service 0535c1
    if not bridge_setting:
Packit Service 0535c1
        bridge_setting = NM.SettingBridge.new()
Packit Service 0535c1
Packit Service 0535c1
    if options:
Packit Service 0535c1
        _set_bridge_properties(bridge_setting, options)
Packit Service 0535c1
Packit Service 0535c1
    bridge_setting.props.vlan_filtering = _is_vlan_filter_active(bridge_state)
Packit Service 0535c1
Packit Service 0535c1
    return bridge_setting
Packit Service 0535c1
Packit Service 0535c1
Packit Service 0535c1
def _get_current_bridge_setting(base_con_profile):
Packit Service 0535c1
    bridge_setting = None
Packit Service 0535c1
    if base_con_profile:
Packit Service 0535c1
        bridge_setting = base_con_profile.get_setting_bridge()
Packit Service 0535c1
        if bridge_setting:
Packit Service 0535c1
            bridge_setting = bridge_setting.duplicate()
Packit Service 0535c1
    return bridge_setting
Packit Service 0535c1
Packit Service 0535c1
Packit Service 0535c1
def _set_bridge_properties(bridge_setting, options):
Packit Service 0535c1
    for key, val in options.items():
Packit Service 0535c1
        if key == LB.Options.MAC_AGEING_TIME:
Packit Service 0535c1
            bridge_setting.props.ageing_time = val
Packit Service 0535c1
        elif key == LB.Options.GROUP_FORWARD_MASK:
Packit Service 0535c1
            bridge_setting.props.group_forward_mask = val
Packit Service 0535c1
        elif key == LB.Options.MULTICAST_SNOOPING:
Packit Service 0535c1
            bridge_setting.props.multicast_snooping = val
Packit Service 0535c1
        elif key == LB.STP_SUBTREE:
Packit Service 0535c1
            _set_bridge_stp_properties(bridge_setting, val)
Packit Service 0535c1
        elif key in NM_BRIDGE_OPTIONS_MAP:
Packit Service 0535c1
            nm_prop_name = NM_BRIDGE_OPTIONS_MAP[key]
Packit Service 0535c1
            # NM is using the sysfs name
Packit Service 0535c1
            if key == LB.Options.GROUP_ADDR:
Packit Service 0535c1
                val = val.lower()
Packit Service 0535c1
            setattr(bridge_setting.props, nm_prop_name, val)
Packit Service 0535c1
Packit Service 0535c1
Packit Service 0535c1
def _set_bridge_stp_properties(bridge_setting, bridge_stp):
Packit Service 0535c1
    bridge_setting.props.stp = bridge_stp[LB.STP.ENABLED]
Packit Service 0535c1
    if bridge_stp[LB.STP.ENABLED] is True:
Packit Service 0535c1
        for stp_key, stp_val in bridge_stp.items():
Packit Service 0535c1
            if stp_key == LB.STP.PRIORITY:
Packit Service 0535c1
                bridge_setting.props.priority = stp_val
Packit Service 0535c1
            elif stp_key == LB.STP.FORWARD_DELAY:
Packit Service 0535c1
                bridge_setting.props.forward_delay = stp_val
Packit Service 0535c1
            elif stp_key == LB.STP.HELLO_TIME:
Packit Service 0535c1
                bridge_setting.props.hello_time = stp_val
Packit Service 0535c1
            elif stp_key == LB.STP.MAX_AGE:
Packit Service 0535c1
                bridge_setting.props.max_age = stp_val
Packit Service 0535c1
Packit Service 0535c1
Packit Service 0535c1
def _is_vlan_filter_active(bridge_state):
Packit Service 0535c1
    return any(
Packit Service 0535c1
        port.get(LB.Port.VLAN_SUBTREE, {}) != {}
Packit Service 0535c1
        for port in bridge_state.get(LB.CONFIG_SUBTREE, {}).get(
Packit Service 0535c1
            LB.PORT_SUBTREE, []
Packit Service 0535c1
        )
Packit Service 0535c1
    )
Packit Service 0535c1
Packit Service 0535c1
Packit Service 0535c1
def create_port_setting(options, base_con_profile):
Packit Service 0535c1
    port_setting = None
Packit Service 0535c1
    if base_con_profile:
Packit Service 0535c1
        port_setting = base_con_profile.get_setting_bridge_port()
Packit Service 0535c1
        if port_setting:
Packit Service 0535c1
            port_setting = port_setting.duplicate()
Packit Service 0535c1
Packit Service 0535c1
    if not port_setting:
Packit Service 0535c1
        port_setting = NM.SettingBridgePort.new()
Packit Service 0535c1
Packit Service 0535c1
    for key, val in options.items():
Packit Service 0535c1
        if key == LB.Port.STP_PRIORITY:
Packit Service 0535c1
            port_setting.props.priority = val
Packit Service 0535c1
        elif key == LB.Port.STP_HAIRPIN_MODE:
Packit Service 0535c1
            port_setting.props.hairpin_mode = val
Packit Service 0535c1
        elif key == LB.Port.STP_PATH_COST:
Packit Service 0535c1
            port_setting.props.path_cost = val
Packit Service 0535c1
        elif key == LB.Port.VLAN_SUBTREE:
Packit Service 0535c1
            port_setting.clear_vlans()
Packit Service 0535c1
            for vlan_config in _create_port_vlans_setting(val):
Packit Service 0535c1
                port_setting.add_vlan(vlan_config)
Packit Service 0535c1
Packit Service 0535c1
    return port_setting
Packit Service 0535c1
Packit Service 0535c1
Packit Service 0535c1
def _create_port_vlans_setting(val):
Packit Service 0535c1
    trunk_tags = val.get(LB.Port.Vlan.TRUNK_TAGS)
Packit Service 0535c1
    tag = val.get(LB.Port.Vlan.TAG)
Packit Service 0535c1
    enable_native_vlan = val.get(LB.Port.Vlan.ENABLE_NATIVE)
Packit Service 0535c1
    port_vlan_config = PortVlanFilter()
Packit Service 0535c1
    port_vlan_config.create_configuration(trunk_tags, tag, enable_native_vlan)
Packit Service 0535c1
    return (vlan_config for vlan_config in port_vlan_config.to_nm())
Packit Service 0535c1
Packit Service 0535c1
Packit Service 0535c1
def get_info(context, nmdev):
Packit Service 0535c1
    """
Packit Service 0535c1
    Provides the current active values for a device
Packit Service 0535c1
    """
Packit Service 0535c1
    info = {}
Packit Service 0535c1
    if nmdev.get_device_type() != NM.DeviceType.BRIDGE:
Packit Service 0535c1
        return info
Packit Service 0535c1
    bridge_setting = _get_bridge_setting(context, nmdev)
Packit Service 0535c1
    if not bridge_setting:
Packit Service 0535c1
        return info
Packit Service 0535c1
Packit Service 0535c1
    port_profiles_by_name = _get_slave_profiles_by_name(nmdev)
Packit Service 0535c1
    port_names_sysfs = _get_slaves_names_from_sysfs(nmdev.get_iface())
Packit Service 0535c1
    props = _get_sysfs_bridge_options(nmdev.get_iface())
Packit Service 0535c1
    info[LB.CONFIG_SUBTREE] = {
Packit Service 0535c1
        LB.PORT_SUBTREE: _get_bridge_ports_info(
Packit Service 0535c1
            port_profiles_by_name,
Packit Service 0535c1
            port_names_sysfs,
Packit Service 0535c1
            vlan_filtering_enabled=bridge_setting.get_vlan_filtering(),
Packit Service 0535c1
        ),
Packit Service 0535c1
        LB.OPTIONS_SUBTREE: {
Packit Service 0535c1
            LB.Options.MAC_AGEING_TIME: props["ageing_time"],
Packit Service 0535c1
            LB.Options.GROUP_FORWARD_MASK: props["group_fwd_mask"],
Packit Service 0535c1
            LB.Options.MULTICAST_SNOOPING: props["multicast_snooping"] > 0,
Packit Service 0535c1
            LB.STP_SUBTREE: {
Packit Service 0535c1
                LB.STP.ENABLED: props["stp_state"] > 0,
Packit Service 0535c1
                LB.STP.PRIORITY: props["priority"],
Packit Service 0535c1
                LB.STP.FORWARD_DELAY: props["forward_delay"],
Packit Service 0535c1
                LB.STP.HELLO_TIME: props["hello_time"],
Packit Service 0535c1
                LB.STP.MAX_AGE: props["max_age"],
Packit Service 0535c1
            },
Packit Service 0535c1
        },
Packit Service 0535c1
    }
Packit Service 0535c1
Packit Service 0535c1
    for schema_name, sysfs_key_name in EXTRA_OPTIONS_MAP.items():
Packit Service 0535c1
        value = props[sysfs_key_name]
Packit Service 0535c1
        if schema_name == LB.Options.GROUP_ADDR:
Packit Service 0535c1
            value = value.upper()
Packit Service 0535c1
        elif schema_name in BOOL_OPTIONS:
Packit Service 0535c1
            value = value > 0
Packit Service 0535c1
        info[LB.CONFIG_SUBTREE][LB.OPTIONS_SUBTREE][schema_name] = value
Packit Service 0535c1
    return info
Packit Service 0535c1
Packit Service 0535c1
Packit Service 0535c1
def get_slaves(nm_device):
Packit Service 0535c1
    return nm_device.get_slaves()
Packit Service 0535c1
Packit Service 0535c1
Packit Service 0535c1
def _get_bridge_setting(context, nmdev):
Packit Service 0535c1
    bridge_setting = None
Packit Service 0535c1
    bridge_con_profile = connection.ConnectionProfile(context)
Packit Service 0535c1
    bridge_con_profile.import_by_device(nmdev)
Packit Service 0535c1
    if bridge_con_profile.profile:
Packit Service 0535c1
        bridge_setting = bridge_con_profile.profile.get_setting_bridge()
Packit Service 0535c1
    return bridge_setting
Packit Service 0535c1
Packit Service 0535c1
Packit Service 0535c1
def _get_bridge_ports_info(
Packit Service 0535c1
    port_profiles_by_name, port_names_sysfs, vlan_filtering_enabled=False
Packit Service 0535c1
):
Packit Service 0535c1
    ports_info_by_name = {
Packit Service 0535c1
        name: _get_bridge_port_info(name) for name in port_names_sysfs
Packit Service 0535c1
    }
Packit Service 0535c1
Packit Service 0535c1
    for name, p in port_profiles_by_name.items():
Packit Service 0535c1
        port_info = ports_info_by_name.get(name, {})
Packit Service 0535c1
        if port_info:
Packit Service 0535c1
            if vlan_filtering_enabled:
Packit Service 0535c1
                bridge_vlan_config = p.get_setting_bridge_port().props.vlans
Packit Service 0535c1
                port_vlan = PortVlanFilter()
Packit Service 0535c1
                port_vlan.import_from_bridge_settings(bridge_vlan_config)
Packit Service 0535c1
                port_info[LB.Port.VLAN_SUBTREE] = port_vlan.to_dict()
Packit Service 0535c1
    return list(ports_info_by_name.values())
Packit Service 0535c1
Packit Service 0535c1
Packit Service 0535c1
def _get_slave_profiles_by_name(master_device):
Packit Service 0535c1
    slaves_profiles_by_name = {}
Packit Service 0535c1
    for dev in master_device.get_slaves():
Packit Service 0535c1
        active_con = connection.get_device_active_connection(dev)
Packit Service 0535c1
        if active_con:
Packit Service 0535c1
            slaves_profiles_by_name[
Packit Service 0535c1
                dev.get_iface()
Packit Service 0535c1
            ] = active_con.props.connection
Packit Service 0535c1
    return slaves_profiles_by_name
Packit Service 0535c1
Packit Service 0535c1
Packit Service 0535c1
def _get_bridge_port_info(port_name):
Packit Service 0535c1
    """Report port runtime information from sysfs."""
Packit Service 0535c1
    port = {LB.Port.NAME: port_name}
Packit Service 0535c1
    for option, option_sysfs in BRIDGE_PORT_NMSTATE_TO_SYSFS.items():
Packit Service 0535c1
        sysfs_path = f"/sys/class/net/{port_name}/brport/{option_sysfs}"
Packit Service 0535c1
        with open(sysfs_path) as f:
Packit Service 0535c1
            option_value = int(f.read())
Packit Service 0535c1
            if option == LB.Port.STP_HAIRPIN_MODE:
Packit Service 0535c1
                option_value = bool(option_value)
Packit Service 0535c1
        port[option] = option_value
Packit Service 0535c1
    return port
Packit Service 0535c1
Packit Service 0535c1
Packit Service 0535c1
def _get_slaves_names_from_sysfs(master):
Packit Service 0535c1
    """
Packit Service 0535c1
    We need to use glob in order to get the slaves name due to bug in
Packit Service 0535c1
    NetworkManager.
Packit Service 0535c1
    Ref: https://bugzilla.redhat.com/show_bug.cgi?id=1809547
Packit Service 0535c1
    """
Packit Service 0535c1
    slaves = []
Packit Service 0535c1
    for sysfs_slave in glob.iglob(f"/sys/class/net/{master}/lower_*"):
Packit Service 0535c1
        # The format is lower_<iface>, we need to remove the "lower_" prefix
Packit Service 0535c1
        prefix_length = len("lower_")
Packit Service 0535c1
        slaves.append(os.path.basename(sysfs_slave)[prefix_length:])
Packit Service 0535c1
    return slaves
Packit Service 0535c1
Packit Service 0535c1
Packit Service 0535c1
def _get_sysfs_bridge_options(iface_name):
Packit Service 0535c1
    user_hz = os.sysconf("SC_CLK_TCK")
Packit Service 0535c1
    options = {}
Packit Service 0535c1
    for sysfs_file_path in glob.iglob(f"/sys/class/net/{iface_name}/bridge/*"):
Packit Service 0535c1
        key = os.path.basename(sysfs_file_path)
Packit Service 0535c1
        try:
Packit Service 0535c1
            with open(sysfs_file_path) as fd:
Packit Service 0535c1
                value = fd.read().rstrip("\n")
Packit Service 0535c1
                options[key] = value
Packit Service 0535c1
                options[key] = int(value, base=0)
Packit Service 0535c1
        except Exception:
Packit Service 0535c1
            pass
Packit Service 0535c1
    for key, value in options.items():
Packit Service 0535c1
        if key in SYSFS_USER_HZ_KEYS:
Packit Service 0535c1
            options[key] = int(value / user_hz)
Packit Service 0535c1
    return options