Mini Shell
#
# utility functions using libnm
#
# Copyright (C) 2018-2023 Red Hat, Inc.
#
# This copyrighted material is made available to anyone wishing to use,
# modify, copy, or redistribute it subject to the terms and conditions of
# the GNU General Public License v.2, or (at your option) any later version.
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY expressed or implied, including the implied warranties of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
# Public License for more details. You should have received a copy of the
# GNU General Public License along with this program; if not, write to the
# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the
# source code or documentation are not subject to the GNU General Public
# License and may only be used or replicated with the express permission of
# Red Hat, Inc.
#
import gi
gi.require_version("NM", "1.0")
from gi.repository import NM
from contextlib import contextmanager
import socket
from pykickstart.constants import BIND_TO_MAC
from pyanaconda.core.glib import create_new_context, GError, sync_call_glib
from pyanaconda.modules.network.constants import NM_CONNECTION_UUID_LENGTH, \
CONNECTION_ADDING_TIMEOUT
from pyanaconda.modules.network.kickstart import default_ks_vlan_interface_name
from pyanaconda.modules.network.utils import is_s390, get_s390_settings, netmask2prefix, \
prefix2netmask
from pyanaconda.core.dbus import SystemBus
from pyanaconda.anaconda_loggers import get_module_logger
log = get_module_logger(__name__)
@contextmanager
def nm_client_in_thread():
"""Create NM Client with new GMainContext to be run in thread.
Expected to be used only in installer environment for a few
one-shot isolated network configuration tasks.
Destroying of the created NM Client instance and release of
related resources is not implemented.
For more information see NetworkManager example examples/python/gi/gmaincontext.py
"""
mainctx = create_new_context()
mainctx.push_thread_default()
try:
yield get_new_nm_client()
finally:
mainctx.pop_thread_default()
def get_new_nm_client():
"""Get new instance of NMClient.
:returns: an instance of NetworkManager NMClient or None if system bus
is not available or NM is not running
:rtype: NM.NMClient
"""
if not SystemBus.check_connection():
log.debug("get new NM Client failed: SystemBus connection check failed.")
return None
try:
nm_client = NM.Client.new(None)
except GError as e:
log.debug("get new NM Client constructor failed: %s", e)
return None
if not nm_client.get_nm_running():
log.debug("get new NM Client failed: NetworkManager is not running.")
return None
log.debug("get new NM Client succeeded.")
return nm_client
def get_iface_from_connection(nm_client, uuid):
"""Get the name of device that would be used for the connection.
In installer it should be just one device.
We need to account also for the case of configurations bound to mac address
(HWADDR), eg network --bindto=mac command.
"""
connection = nm_client.get_connection_by_uuid(uuid)
if not connection:
return None
iface = connection.get_setting_connection().get_interface_name()
if not iface:
wired_setting = connection.get_setting_wired()
if wired_setting:
mac = wired_setting.get_mac_address()
if mac:
iface = get_iface_from_hwaddr(nm_client, mac)
return iface
def get_vlan_interface_name_from_connection(nm_client, connection):
"""Get vlan interface name from vlan connection.
:param connection: NetworkManager connection
:type connection: NM.RemoteConnection
If no interface name is specified in the connection settings, infer the
value as <PARENT_IFACE>.<VLAN_ID> - same as NetworkManager.
"""
iface = connection.get_setting_connection().get_interface_name()
if not iface:
setting_vlan = connection.get_setting_vlan()
if setting_vlan:
vlanid = setting_vlan.get_id()
parent = setting_vlan.get_parent()
# if parent is specified by UUID
if len(parent) == NM_CONNECTION_UUID_LENGTH:
parent = get_iface_from_connection(nm_client, parent)
if vlanid is not None and parent:
iface = default_ks_vlan_interface_name(parent, vlanid)
return iface
def get_iface_from_hwaddr(nm_client, hwaddr):
"""Find the name of device specified by mac address."""
for device in nm_client.get_devices():
if device.get_device_type() in (NM.DeviceType.ETHERNET,
NM.DeviceType.WIFI):
try:
address = device.get_permanent_hw_address()
if not address:
address = device.get_hw_address()
except AttributeError as e:
log.warning("Device %s: %s", device.get_iface(), e)
address = device.get_hw_address()
else:
address = device.get_hw_address()
# per #1703152, at least in *some* case, we wind up with
# address as None here, so we need to guard against that
if address and address.upper() == hwaddr.upper():
return device.get_iface()
return None
def get_team_port_config_from_connection(nm_client, uuid):
connection = nm_client.get_connection_by_uuid(uuid)
if not connection:
return None
team_port = connection.get_setting_team_port()
if not team_port:
return None
config = team_port.get_config()
return config
def get_team_config_from_connection(nm_client, uuid):
connection = nm_client.get_connection_by_uuid(uuid)
if not connection:
return None
team = connection.get_setting_team()
if not team:
return None
config = team.get_config()
return config
def get_device_name_from_network_data(nm_client, network_data, supported_devices, bootif):
"""Get the device name from kickstart device specification.
Generally given by --device option. For vlans also --interfacename
and --vlanid comes into play.
Side effect: for vlan sets network_data.parent value from --device option
:param network_data: a kickstart device configuartion
:type network_data: kickstart NetworkData object
:param supported_devices: list of names of supported devices
:type supported_devices: list(str)
:param bootif: MAC addres of device to be used for --device=bootif specification
:type bootif: str
:returns: device name the configuration should be used for
:rtype: str
"""
spec = network_data.device
device_name = ""
msg = ""
if not spec:
msg = "device specification missing"
# Specification by device name
if spec in supported_devices:
device_name = spec
msg = "existing device found"
# Specification by mac address
elif ':' in spec:
device_name = get_iface_from_hwaddr(nm_client, spec) or ""
msg = "existing device found"
# Specification by BOOTIF boot option
elif spec == 'bootif':
if bootif:
device_name = get_iface_from_hwaddr(nm_client, bootif) or ""
msg = "existing device for {} found".format(bootif)
else:
msg = "BOOTIF value is not specified in boot options"
# First device with carrier (sorted lexicographically)
elif spec == 'link':
device_name = get_first_iface_with_link(nm_client, supported_devices) or ""
msg = "first device with link found"
if device_name:
if device_name not in supported_devices:
msg = "{} device found is not supported".format(device_name)
device_name = ""
# Virtual devices don't have to exist
elif spec and any((network_data.vlanid,
network_data.bondslaves,
network_data.teamslaves,
network_data.bridgeslaves)):
device_name = spec
msg = "virtual device does not exist, which is OK"
if network_data.vlanid:
network_data.parent = device_name
if network_data.interfacename:
device_name = network_data.interfacename
msg = "vlan device name specified by --interfacename"
else:
device_name = default_ks_vlan_interface_name(device_name, network_data.vlanid)
msg = "vlan device name inferred from parent and vlanid"
log.debug("kickstart specification --device=%s -> %s (%s)", spec, device_name, msg)
return device_name
def _update_bond_connection_from_ksdata(connection, network_data):
"""Update connection with values from bond kickstart configuration.
:param connection: connection to be updated before adding to NM
:type connection: NM.SimpleConnection
:param network_data: kickstart configuration
:type network_data: pykickstart NetworkData
"""
s_con = connection.get_setting_connection()
s_con.props.type = "bond"
s_bond = NM.SettingBond.new()
opts = network_data.bondopts
for option in opts.split(';' if ';' in opts else ','):
key, _sep, value = option.partition("=")
if not s_bond.add_option(key, value):
log.warning("adding bond option %s failed (invalid?)")
connection.add_setting(s_bond)
def _update_team_connection_from_ksdata(connection, network_data):
"""Update connection with values from team kickstart configuration.
:param connection: connection to be updated before adding to NM
:type connection: NM.SimpleConnection
:param network_data: kickstart configuration
:type network_data: pykickstart NetworkData
"""
s_con = connection.get_setting_connection()
s_con.props.type = "team"
s_team = NM.SettingTeam.new()
s_team.props.config = network_data.teamconfig
connection.add_setting(s_team)
def _update_vlan_connection_from_ksdata(connection, network_data):
"""Update connection with values from vlan kickstart configuration.
:param connection: connection to be updated before adding to NM
:type connection: NM.SimpleConnection
:param network_data: kickstart configuration
:type network_data: pykickstart NetworkData
:returns: interface name of the device
:rtype: str
"""
s_con = connection.get_setting_connection()
s_con.props.type = "vlan"
if network_data.interfacename:
s_con.props.id = network_data.interfacename
s_con.props.interface_name = network_data.interfacename
else:
s_con.props.interface_name = None
s_vlan = NM.SettingVlan.new()
s_vlan.props.id = int(network_data.vlanid)
s_vlan.props.parent = network_data.parent
connection.add_setting(s_vlan)
return s_con.props.interface_name
def _update_bridge_connection_from_ksdata(connection, network_data):
"""Update connection with values from bridge kickstart configuration.
:param connection: connection to be updated before adding to NM
:type connection: NM.SimpleConnection
:param network_data: kickstart configuration
:type network_data: pykickstart NetworkData
"""
s_con = connection.get_setting_connection()
s_con.props.type = "bridge"
s_bridge = NM.SettingBridge.new()
for opt in network_data.bridgeopts.split(","):
key, _sep, value = opt.partition("=")
if key in ("stp", "multicast-snooping"):
if value == "yes":
value = True
elif value == "no":
value = False
else:
try:
value = int(value)
except ValueError:
log.error("Invalid bridge option %s", opt)
continue
s_bridge.set_property(key, value)
connection.add_setting(s_bridge)
def _update_infiniband_connection_from_ksdata(connection, network_data):
"""Update connection with values from infiniband kickstart configuration.
:param connection: connection to be updated before adding to NM
:type connection: NM.SimpleConnection
:param network_data: kickstart configuration
:type network_data: pykickstart NetworkData
"""
s_con = connection.get_setting_connection()
s_con.props.type = "infiniband"
s_ib = NM.SettingInfiniband.new()
s_ib.props.transport_mode = "datagram"
connection.add_setting(s_ib)
def _update_ethernet_connection_from_ksdata(connection, network_data, bound_mac):
"""Update connection with values from ethernet kickstart configuration.
:param connection: connection to be updated before adding to NM
:type connection: NM.SimpleConnection
:param network_data: kickstart configuration
:type network_data: pykickstart NetworkData
:param bound_mac: MAC address the device name is bound to (ifname=)
:type bound_mac: str
"""
s_con = connection.get_setting_connection()
s_con.props.type = "802-3-ethernet"
s_wired = NM.SettingWired.new()
if bound_mac:
s_wired.props.mac_address = bound_mac
connection.add_setting(s_wired)
def _update_wired_connection_with_s390_settings(connection, s390cfg):
"""Update connection with values specific for s390 architecture.
:param connection: connection to be updated before adding to NM
:type connection: NM.SimpleConnection
:param s390cfg: dictionary storing s390 specific settings
:type s390cfg: dict
"""
s_wired = connection.get_setting_wired()
if s390cfg['SUBCHANNELS']:
subchannels = s390cfg['SUBCHANNELS'].split(",")
s_wired.props.s390_subchannels = subchannels
if s390cfg['NETTYPE']:
s_wired.props.s390_nettype = s390cfg['NETTYPE']
if s390cfg['OPTIONS']:
opts = s390cfg['OPTIONS'].split(" ")
opts_dict = {k: v for k, v in (o.split("=") for o in opts)}
s_wired.props.s390_options = opts_dict
def create_connections_from_ksdata(nm_client, network_data, device_name, ifname_option_values=None):
"""Create NM connections from kickstart configuration.
:param network_data: kickstart configuration
:type network_data: pykickstart NetworkData
:param device_name: name of the device to be configured by kickstart
:type device_name: str
:param ifname_option_values: list of ifname boot option values
:type ifname_option_values: list(str)
:return: list of tuples (CONNECTION, NAME_OF_DEVICE_TO_BE_ACTIVATED)
:rtype: list((NM.RemoteConnection, str))
"""
ifname_option_values = ifname_option_values or []
connections = []
device_to_activate = device_name
con_uuid = NM.utils_uuid_generate()
con = NM.SimpleConnection.new()
update_connection_ip_settings_from_ksdata(con, network_data)
s_con = NM.SettingConnection.new()
s_con.props.uuid = con_uuid
s_con.props.id = device_name
s_con.props.interface_name = device_name
s_con.props.autoconnect = network_data.onboot
con.add_setting(s_con)
# type "bond"
if network_data.bondslaves:
_update_bond_connection_from_ksdata(con, network_data)
for i, slave in enumerate(network_data.bondslaves.split(","), 1):
slave_con = create_slave_connection('bond', i, slave, device_name)
bind_connection(nm_client, slave_con, network_data.bindto, slave)
connections.append((slave_con, slave))
# type "team"
elif network_data.teamslaves:
_update_team_connection_from_ksdata(con, network_data)
for i, (slave, cfg) in enumerate(network_data.teamslaves, 1):
s_team_port = NM.SettingTeamPort.new()
s_team_port.props.config = cfg
slave_con = create_slave_connection('team', i, slave, device_name,
settings=[s_team_port])
bind_connection(nm_client, slave_con, network_data.bindto, slave)
connections.append((slave_con, slave))
# type "vlan"
elif network_data.vlanid:
device_to_activate = _update_vlan_connection_from_ksdata(con, network_data)
# type "bridge"
elif network_data.bridgeslaves:
# bridge connection is autoactivated
_update_bridge_connection_from_ksdata(con, network_data)
for i, slave in enumerate(network_data.bridgeslaves.split(","), 1):
slave_con = create_slave_connection('bridge', i, slave, device_name)
bind_connection(nm_client, slave_con, network_data.bindto, slave)
connections.append((slave_con, slave))
# type "infiniband"
elif is_infiniband_device(nm_client, device_name):
_update_infiniband_connection_from_ksdata(con, network_data)
# type "802-3-ethernet"
else:
bound_mac = bound_hwaddr_of_device(nm_client, device_name, ifname_option_values)
_update_ethernet_connection_from_ksdata(con, network_data, bound_mac)
if bound_mac:
log.debug("add connection: mac %s is bound to name %s",
bound_mac, device_name)
else:
bind_connection(nm_client, con, network_data.bindto, device_name)
# Add s390 settings
if is_s390():
s390cfg = get_s390_settings(device_name)
_update_wired_connection_with_s390_settings(con, s390cfg)
update_connection_wired_settings_from_ksdata(con, network_data)
connections.insert(0, (con, device_to_activate))
return connections
def add_connection_from_ksdata(nm_client, network_data, device_name, activate=False,
ifname_option_values=None):
"""Add NM connection created from kickstart configuration.
:param network_data: kickstart configuration
:type network_data: pykickstart NetworkData
:param device_name: name of the device to be configured by kickstart
:type device_name: str
:param activate: activate the added connection
:type activate: bool
:param ifname_option_values: list of ifname boot option values
:type ifname_option_values: list(str)
"""
connections = create_connections_from_ksdata(
nm_client,
network_data,
device_name,
ifname_option_values
)
for connection, device_name in connections:
log.debug("add connection (activate=%s): %s for %s\n%s",
activate, connection.get_uuid(), device_name,
connection.to_dbus(NM.ConnectionSerializationFlags.NO_SECRETS))
added_connection = add_connection_sync(
nm_client,
connection,
)
if not added_connection:
continue
if activate:
if device_name:
device = nm_client.get_device_by_iface(device_name)
if device:
log.debug("activating with device %s", device.get_iface())
else:
log.debug("activating without device specified - device %s not found",
device_name)
else:
device = None
log.debug("activating without device specified")
nm_client.activate_connection_async(added_connection, device, None, None)
return connections
def add_connection_sync(nm_client, connection):
"""Add a connection synchronously.
Synchronous run is implemented by running a blocking GMainLoop with
GMainContext belonging to the nm_client created for the calling Task.
:param nm_client: NetoworkManager client
:type nm_client: NM.NMClient
:param connection: connection to be added
:type connection: NM.SimpleConnection
:return: added connection or None on timeout
:rtype: NM.RemoteConnection
"""
result = sync_call_glib(
nm_client.get_main_context(),
nm_client.add_connection2,
nm_client.add_connection2_finish,
CONNECTION_ADDING_TIMEOUT,
connection.to_dbus(NM.ConnectionSerializationFlags.ALL),
(NM.SettingsAddConnection2Flags.TO_DISK |
NM.SettingsAddConnection2Flags.BLOCK_AUTOCONNECT),
None,
False
)
if result.failed:
log.error("adding of a connection %s failed: %s",
connection.get_uuid(),
result.error_message)
return None
con, _res = result.received_data
log.debug("connection %s added:\n%s", connection.get_uuid(),
connection.to_dbus(NM.ConnectionSerializationFlags.NO_SECRETS))
return con
def create_slave_connection(slave_type, slave_idx, slave, master, settings=None):
"""Create a slave NM connection for virtual connection (bond, team, bridge).
:param slave_type: type of slave ("bond", "team", "bridge")
:type slave_type: str
:param slave_idx: index of the slave for naming
:type slave_idx: int
:param slave: slave's device name
:type slave: str
:param master: slave's master device name
:type master: str
:param settings: list of other settings to be added to the connection
:type settings: list(NM.Setting)
:return: created connection
:rtype: NM.SimpleConnection
"""
settings = settings or []
slave_name = "%s slave %d" % (master, slave_idx)
con = NM.SimpleConnection.new()
s_con = NM.SettingConnection.new()
s_con.props.uuid = NM.utils_uuid_generate()
s_con.props.id = slave_name
s_con.props.slave_type = slave_type
s_con.props.master = master
s_con.props.type = '802-3-ethernet'
# HACK preventing NM to autoactivate the connection
# The real network --onboot value (ifcfg ONBOOT) will be set later by
# update_onboot
s_con.props.autoconnect = False
con.add_setting(s_con)
s_wired = NM.SettingWired.new()
con.add_setting(s_wired)
for setting in settings:
con.add_setting(setting)
return con
def is_infiniband_device(nm_client, device_name):
"""Is the type of the device infiniband?"""
device = nm_client.get_device_by_iface(device_name)
if device and device.get_device_type() == NM.DeviceType.INFINIBAND:
return True
return False
def is_ibft_connection(connection):
"""Is the connection generated by NM from iBFT?"""
return connection.get_id().startswith("iBFT Connection")
def bound_hwaddr_of_device(nm_client, device_name, ifname_option_values):
"""Check and return mac address of device bound by device renaming.
For example ifname=ens3:f4:ce:46:2c:44:7a should bind the device name ens3
to the MAC address (and rename the device in initramfs eventually). If
hwaddress of the device devname is the same as the MAC address, its value
is returned.
:param device_name: device name
:type device_name: str
:param ifname_option_values: list of ifname boot option values
:type ifname_option_values: list(str)
:return: hwaddress of the device if bound, or None
:rtype: str or None
"""
for ifname_value in ifname_option_values:
iface, mac = ifname_value.split(":", 1)
if iface == device_name:
if iface == get_iface_from_hwaddr(nm_client, mac):
return mac.upper()
else:
log.warning("MAC address of ifname %s does not correspond to ifname=%s",
iface, ifname_value)
return None
def update_connection_from_ksdata(nm_client, connection, network_data, device_name=None):
"""Update NM connection specified by uuid from kickstart configuration.
:param connection: existing NetworkManager connection to be updated
:type connection: NM.RemoteConnection
:param network_data: kickstart network configuration
:type network_data: pykickstart NetworkData
:param device_name: device name the connection should be bound to eventually
:type device_name: str
"""
log.debug("updating connection %s:\n%s", connection.get_uuid(),
connection.to_dbus(NM.ConnectionSerializationFlags.NO_SECRETS))
# IP configuration
update_connection_ip_settings_from_ksdata(connection, network_data)
update_connection_wired_settings_from_ksdata(connection, network_data)
s_con = connection.get_setting_connection()
s_con.set_property(NM.SETTING_CONNECTION_AUTOCONNECT, network_data.onboot)
if connection.get_connection_type() not in ("bond", "team", "vlan", "bridge"):
bind_connection(nm_client, connection, network_data.bindto, device_name)
commit_changes_with_autoconnection_blocked(connection, nm_client)
log.debug("updated connection %s:\n%s", connection.get_uuid(),
connection.to_dbus(NM.ConnectionSerializationFlags.NO_SECRETS))
def update_connection_ip_settings_from_ksdata(connection, network_data):
"""Update NM connection from kickstart IP configuration in place.
:param connection: existing NetworkManager connection to be updated
:type connection: NM.RemoteConnection
:param network_data: kickstart configuation containing the IP configuration
to be applied to the connection
:type network_data: pykickstart NetworkData
"""
# ipv4 settings
if network_data.noipv4:
method4 = "disabled"
elif network_data.bootProto == "static":
method4 = "manual"
else:
method4 = "auto"
connection.remove_setting(NM.SettingIP4Config)
s_ip4 = NM.SettingIP4Config.new()
s_ip4.set_property(NM.SETTING_IP_CONFIG_METHOD, method4)
if method4 == "manual":
prefix4 = netmask2prefix(network_data.netmask)
addr4 = NM.IPAddress.new(socket.AF_INET, network_data.ip, prefix4)
s_ip4.add_address(addr4)
if network_data.gateway:
s_ip4.props.gateway = network_data.gateway
if network_data.nodefroute:
s_ip4.props.never_default = True
connection.add_setting(s_ip4)
# ipv6 settings
if network_data.noipv6:
method6 = "ignore"
elif not network_data.ipv6 or network_data.ipv6 == "auto":
method6 = "auto"
elif network_data.ipv6 == "dhcp":
method6 = "dhcp"
else:
method6 = "manual"
connection.remove_setting(NM.SettingIP6Config)
s_ip6 = NM.SettingIP6Config.new()
s_ip6.set_property(NM.SETTING_IP_CONFIG_METHOD, method6)
s_ip6.set_property(NM.SETTING_IP6_CONFIG_ADDR_GEN_MODE,
NM.SettingIP6ConfigAddrGenMode.EUI64)
if method6 == "manual":
addr6, _slash, prefix6 = network_data.ipv6.partition("/")
if prefix6:
prefix6 = int(prefix6)
else:
prefix6 = 64
addr6 = NM.IPAddress.new(socket.AF_INET6, addr6, prefix6)
s_ip6.add_address(addr6)
if network_data.ipv6gateway:
s_ip6.props.gateway = network_data.ipv6gateway
connection.add_setting(s_ip6)
# nameservers
if network_data.nameserver:
for ns in [str.strip(i) for i in network_data.nameserver.split(",")]:
if NM.utils_ipaddr_valid(socket.AF_INET6, ns):
s_ip6.add_dns(ns)
elif NM.utils_ipaddr_valid(socket.AF_INET, ns):
s_ip4.add_dns(ns)
else:
log.error("IP address %s is not valid", ns)
# DNS search domains
if network_data.ipv4_dns_search:
for domain in [str.strip(i) for i in network_data.ipv4_dns_search.split(",")]:
s_ip4.add_dns_search(domain)
if network_data.ipv6_dns_search:
for domain in [str.strip(i) for i in network_data.ipv6_dns_search.split(",")]:
s_ip6.add_dns_search(domain)
# ignore auto DNS
if network_data.ipv4_ignore_auto_dns:
s_ip4.props.ignore_auto_dns = network_data.ipv4_ignore_auto_dns
if network_data.ipv6_ignore_auto_dns:
s_ip6.props.ignore_auto_dns = network_data.ipv6_ignore_auto_dns
def update_connection_wired_settings_from_ksdata(connection, network_data):
"""Update NM connection wired settings from kickstart in place.
:param connection: existing NetworkManager connection to be updated
:type connection: NM.RemoteConnection
:param network_data: kickstart configuation to be applied to the connection
:type network_data: pykickstart NetworkData
"""
if network_data.mtu:
try:
mtu = int(network_data.mtu)
except ValueError:
log.error("Value of network --mtu option is not valid: %s", network_data.mtu)
else:
s_wired = connection.get_setting_wired()
if not s_wired:
s_wired = NM.SettingWired.new()
connection.add_setting(s_wired)
s_wired.props.mtu = mtu
def bind_settings_to_mac(nm_client, s_connection, s_wired, device_name=None, bind_exclusively=True):
"""Bind the settings to the mac address of the device.
:param s_connection: connection setting to be updated
:type s_connection: NM.SettingConnection
:param s_wired: wired setting to be updated
:type s_wired: NM.SettingWired
:param device_name: name of the device to be bound
:type device_name: str
:param bind_exclusively: remove reference to the device name from the settings
:type bind_exclusively: bool
:returns: True if the settings were modified, False otherwise
:rtype: bool
"""
mac_address = s_wired.get_mac_address()
interface_name = s_connection.get_interface_name()
modified = False
if mac_address:
log.debug("Bind to mac: already bound to %s", mac_address)
else:
iface = device_name or interface_name
if not iface:
log.warning("Bind to mac: no device name provided to look for mac")
return False
device = nm_client.get_device_by_iface(iface)
if device:
hwaddr = device.get_permanent_hw_address() or device.get_hw_address()
s_wired.props.mac_address = hwaddr
log.debug("Bind to mac: bound to %s", hwaddr)
modified = True
if bind_exclusively and interface_name:
s_connection.props.interface_name = None
log.debug("Bind to mac: removed interface-name %s from connection", interface_name)
modified = True
return modified
def bind_settings_to_device(nm_client, s_connection, s_wired, device_name=None,
bind_exclusively=True):
"""Bind the settings to the name of the device.
:param s_connection: connection setting to be updated
:type s_connection: NM.SettingConnection
:param s_wired: wired setting to be updated
:type s_wired: NM.SettingWired
:param device_name: name of the device to be bound
:type device_name: str
:param bind_exclusively: remove reference to the mac address from the settings
:type bind_exclusively: bool
:returns: True if the settings were modified, False otherwise
:rtype: bool
"""
mac_address = s_wired.get_mac_address()
interface_name = s_connection.get_interface_name()
modified = False
if device_name:
s_connection.props.interface_name = device_name
log.debug("Bind to device: %s -> %s", interface_name, device_name)
modified = interface_name != device_name
else:
if not interface_name:
log.debug("Bind to device: no device to bind to")
return False
else:
log.debug("Bind to device: already bound to %s", interface_name)
if bind_exclusively and mac_address:
s_wired.props.mac_address = None
log.debug("Bind to device: removed mac-address from connection")
modified = True
return modified
def bind_connection(nm_client, connection, bindto, device_name=None, bind_exclusively=True):
"""Bind the connection to device name or mac address.
:param connection: connection to be updated before adding to NM
:type connection: NM.SimpleConnection
:param bindto: type of binding of the connection (mac address of device name)
- BIND_TO_MAC for mac address
- None for device name (default)
:type bindto: pykickstart --bindto constant
:param device_name: device name for binding
:type device_name: str
:param bind_exclusively: when binding to an entity, remove reference to the other
:type bind_exclusively: bool
:returns: True if the connection was modified, False otherwise
:rtype: bool
"""
msg = "Bind connection {} to {}:".format(connection.get_uuid(), bindto or "iface")
s_con = connection.get_setting_connection()
if not s_con:
log.warning("%s no connection settings, bailing", msg)
return False
s_wired = connection.get_setting_wired()
if bindto == BIND_TO_MAC:
if not s_wired:
log.warning("%s no wired settings, bailing", msg)
return False
modified = bind_settings_to_mac(nm_client, s_con, s_wired, device_name, bind_exclusively)
else:
modified = bind_settings_to_device(nm_client, s_con, s_wired, device_name, bind_exclusively)
return modified
def ensure_active_connection_for_device(nm_client, uuid, device_name, only_replace=False):
"""Make sure active connection of a device is the one specified by uuid.
:param uuid: uuid of the connection to be applied
:type uuid: str
:param device_name: name of device to apply the connection to
:type device_name: str
:param only_replace: apply the connection only if the device has different
active connection
:type only_replace: bool
"""
activated = False
active_uuid = None
device = nm_client.get_device_by_iface(device_name)
if device:
ac = device.get_active_connection()
if ac or not only_replace:
active_uuid = ac.get_uuid() if ac else None
if uuid != active_uuid:
ifcfg_con = nm_client.get_connection_by_uuid(uuid)
if ifcfg_con:
nm_client.activate_connection_async(ifcfg_con, None, None, None)
activated = True
msg = "activated" if activated else "not activated"
log.debug("ensure active ifcfg connection for %s (%s -> %s): %s",
device_name, active_uuid, uuid, msg)
return activated
def get_connections_available_for_iface(nm_client, iface):
"""Get all connections available for given interface.
:param iface: interface name
:type iface: str
:return: list of all available connections
:rtype: list(NM.RemoteConnection)
"""
cons = []
device = nm_client.get_device_by_iface(iface)
if device:
cons = device.get_available_connections()
else:
# Try also non-existing (not real) virtual devices
for device in nm_client.get_all_devices():
if not device.is_real() and device.get_iface() == iface:
cons = device.get_available_connections()
if cons:
break
else:
# Getting available connections does not seem to work quite well for
# non-real team - try to look them up in all connections.
for con in nm_client.get_connections():
interface_name = con.get_interface_name()
if not interface_name and con.get_connection_type() == "vlan":
interface_name = get_vlan_interface_name_from_connection(nm_client, con)
if interface_name == iface:
cons.append(con)
return cons
def update_connection_values(connection, new_values):
"""Update setting values of a connection.
:param connection: existing NetworkManager connection to be updated
:type connection: NM.RemoteConnection
:param new_values: list of properties to be updated
:type new_values: [(SETTING_NAME, SETTING_PROPERTY, VALUE)]
"""
for setting_name, setting_property, value in new_values:
setting = connection.get_setting_by_name(setting_name)
if setting:
setting.set_property(setting_property, value)
log.debug("updating connection %s setting '%s' '%s' to '%s'",
connection.get_uuid(), setting_name, setting_property, value)
else:
log.debug("setting '%s' not found while updating connection %s",
setting_name, connection.get_uuid())
log.debug("updated connection %s:\n%s", connection.get_uuid(),
connection.to_dbus(NM.ConnectionSerializationFlags.ALL))
def devices_ignore_ipv6(nm_client, device_types):
"""All connections of devices of given type ignore ipv6."""
device_types = device_types or []
for device in nm_client.get_devices():
if device.get_device_type() in device_types:
cons = device.get_available_connections()
for con in cons:
s_ipv6 = con.get_setting_ip6_config()
if s_ipv6 and s_ipv6.get_method() != NM.SETTING_IP6_CONFIG_METHOD_IGNORE:
return False
return True
def get_first_iface_with_link(nm_client, ifaces):
"""Find first iface having link (in lexicographical order)."""
for iface in sorted(ifaces):
device = nm_client.get_device_by_iface(iface)
if device:
try:
carrier = device.get_carrier()
except AttributeError:
carrier = None
if carrier:
return device.get_iface()
return None
def get_connections_dump(nm_client):
"""Dumps all connections for logging."""
con_dumps = []
for con in nm_client.get_connections():
con_dumps.append(str(con.to_dbus(NM.ConnectionSerializationFlags.NO_SECRETS)))
return "\n".join(con_dumps)
def commit_changes_with_autoconnection_blocked(connection, nm_client, save_to_disk=True):
"""Implementation of NM CommitChanges() method with blocked autoconnection.
Update2() API is used to implement the functionality.
Prevents autoactivation of the connection on its update which would happen
with CommitChanges if "autoconnect" is set True.
Synchronous run is implemented by running a blocking GMainLoop with
GMainContext belonging to the nm_client created for the calling Task.
:param connection: NetworkManager connection
:type connection: NM.RemoteConnection
:param nm_client: NetoworkManager client
:type nm_client: NM.NMClient
:param save_to_disk: should the changes be written also to disk?
:type save_to_disk: bool
:return: on success result of the Update2() call, None of failure
:rtype: GVariant of type "a{sv}" or None
"""
flags = NM.SettingsUpdate2Flags.BLOCK_AUTOCONNECT
if save_to_disk:
flags |= NM.SettingsUpdate2Flags.TO_DISK
con2 = NM.SimpleConnection.new_clone(connection)
result = sync_call_glib(
nm_client.get_main_context(),
connection.update2,
connection.update2_finish,
CONNECTION_ADDING_TIMEOUT,
con2.to_dbus(NM.ConnectionSerializationFlags.ALL),
flags,
None
)
if result.failed:
log.error("comitting changes of connection %s failed: %s",
connection.get_uuid(),
result.error_message)
return None
return result.received_data
def clone_connection_sync(nm_client, connection, con_id=None, uuid=None):
"""Clone a connection synchronously.
:param connection: NetworkManager connection
:type connection: NM.RemoteConnection
:param con_id: id of the cloned connection
:type con_id: str
:param uuid: uuid of the cloned connection (None to be generated)
:type uuid: str
:return: NetworkManager connection or None on timeout
:rtype: NM.RemoteConnection
"""
cloned_connection = NM.SimpleConnection.new_clone(connection)
s_con = cloned_connection.get_setting_connection()
s_con.props.uuid = uuid or NM.utils_uuid_generate()
s_con.props.id = con_id or "{}-clone".format(connection.get_id())
log.debug("cloning connection %s", connection.get_uuid())
added_connection = add_connection_sync(nm_client, cloned_connection)
if added_connection:
log.debug("connection was cloned into %s", added_connection.get_uuid())
else:
log.debug("connection cloning failed")
return added_connection
def get_dracut_arguments_from_connection(nm_client, connection, iface, target_ip,
hostname, ibft=False):
"""Get dracut arguments for the iface and SAN target from NM connection.
Examples of SAN: iSCSI, FCoE
The dracut arguments would activate the iface in initramfs so that the
SAN target can be attached (usually to mount root filesystem).
:param nm_client: instance of NetworkManager client
:type nm_client: NM.Client
:param connection: NetworkManager connection
:type connection: NM.RemoteConnection
:param iface: network interface used to connect to the target
(can be none if ibft is used)
:type iface: str
:param target_ip: IP of the SAN target
:type target_ip: str
:param hostname: static hostname to be configured
:type hostname: str
:param ibft: network should be configured from ibft
:type ibft: bool
:returns: dracut arguments
:rtype: set(str)
"""
netargs = set()
if ibft:
netargs.add("rd.iscsi.ibft")
elif target_ip:
if hostname is None:
hostname = ""
if ':' in target_ip:
# Using IPv6 target IP
ipv6_arg = _get_dracut_ipv6_argument(connection, iface, hostname)
if ipv6_arg:
netargs.add(ipv6_arg)
else:
log.error("No IPv6 configuration found in connection %s", connection.get_uuid())
else:
# Using IPv4 target IP
ipv4_arg = _get_dracut_ipv4_argument(connection, iface, hostname)
if ipv4_arg:
netargs.add(ipv4_arg)
else:
log.error("No IPv4 configuration found in connection %s", connection.get_uuid())
ifname_arg = _get_dracut_ifname_argument_from_connection(connection, iface)
if ifname_arg:
netargs.add(ifname_arg)
team_arg = _get_dracut_team_argument_from_connection(nm_client, connection, iface)
if team_arg:
netargs.add(team_arg)
vlan_arg, vlan_parent_connection = _get_dracut_vlan_argument_from_connection(nm_client,
connection,
iface)
if vlan_arg:
netargs.add(vlan_arg)
# For vlan the parent connection defines the s390 znet argument values
if vlan_parent_connection:
connection = vlan_parent_connection
znet_arg = _get_dracut_znet_argument_from_connection(connection)
if znet_arg:
netargs.add(znet_arg)
return netargs
def _get_dracut_ipv6_argument(connection, iface, hostname):
"""Get dracut ip IPv6 configuration for given interface and NM connection.
:param connection: NetworkManager connection
:type connection: NM.RemoteConnection
:param iface: network interface to be used
:type iface: str
:param hostname: static hostname to be configured
:type hostname: str
:returns: dracut ip argument or "" if the configuration can't be find
:rtype: set(str)
"""
argument = ""
ip6_config = connection.get_setting_ip6_config()
ip6_method = ip6_config.get_method()
if ip6_method == NM.SETTING_IP6_CONFIG_METHOD_AUTO:
argument = "ip={}:auto6".format(iface)
elif ip6_method == NM.SETTING_IP6_CONFIG_METHOD_DHCP:
# Most probably not working
argument = "ip={}:dhcp6".format(iface)
elif ip6_method == NM.SETTING_IP6_CONFIG_METHOD_MANUAL:
ipaddr = ""
if ip6_config.get_num_addresses() > 0:
addr = ip6_config.get_address(0)
ipaddr = "[{}/{}]".format(addr.get_address(), addr.get_prefix())
gateway = ip6_config.get_gateway() or ""
if gateway:
gateway = "[{}]".format(gateway)
if ipaddr or gateway:
argument = ("ip={}::{}::{}:{}:none".format(ipaddr, gateway, hostname, iface))
return argument
def _get_dracut_ipv4_argument(connection, iface, hostname):
"""Get dracut ip IPv4 configuration for given interface and NM connection.
:param connection: NetworkManager connection
:type connection: NM.RemoteConnection
:param iface: network interface to be used
:type iface: str
:param hostname: static hostname to be configured
:type hostname: str
:returns: dracut ip argument or "" if the configuration can't be find
:rtype: str
"""
argument = ""
ip4_config = connection.get_setting_ip4_config()
ip4_method = ip4_config.get_method()
if ip4_method == NM.SETTING_IP4_CONFIG_METHOD_AUTO:
argument = "ip={}:dhcp".format(iface)
elif ip4_method == NM.SETTING_IP4_CONFIG_METHOD_MANUAL:
if ip4_config.get_num_addresses() > 0:
addr = ip4_config.get_address(0)
ip = addr.get_address()
netmask = prefix2netmask(addr.get_prefix())
gateway = ip4_config.get_gateway() or ""
argument = "ip={}::{}:{}:{}:{}:none".format(ip, gateway, netmask, hostname, iface)
return argument
def _get_dracut_ifname_argument_from_connection(connection, iface):
"""Get dracut ifname configuration for given interface and NM connection.
:param connection: NetworkManager connection
:type connection: NM.RemoteConnection
:param iface: network interface to be used
:type iface: str
:returns: dracut ifname argument or "" if the configuration does not apply
:rtype: str
"""
argument = ""
wired_setting = connection.get_setting_wired()
if wired_setting:
hwaddr = wired_setting.get_mac_address()
if hwaddr:
argument = "ifname={}:{}".format(iface, hwaddr.lower())
return argument
def _get_dracut_team_argument_from_connection(nm_client, connection, iface):
"""Get dracut team configuration for given interface and NM connection.
:param nm_client: instance of NetworkManager client
:type nm_client: NM.Client
:param connection: NetworkManager connection
:type connection: NM.RemoteConnection
:param iface: network interface to be used
:type iface: str
:returns: dracut team argument or "" if the configuration does not apply
:rtype: str
"""
argument = ""
if connection.get_connection_type() == "team":
slaves = sorted(get_slaves_from_connections(
nm_client,
"team",
[iface, connection.get_uuid()]
))
argument = "team={}:{}".format(iface, ",".join(s_iface for s_iface, _uuid in slaves))
return argument
def _get_dracut_vlan_argument_from_connection(nm_client, connection, iface):
"""Get dracut vlan configuration for given interface and NM connection.
Returns also parent vlan connection.
:param nm_client: instance of NetworkManager client
:type nm_client: NM.Client
:param connection: NetworkManager connection
:type connection: NM.RemoteConnection
:param iface: network interface to be used
:type iface: str
:returns: tuple (ARGUMENT, PARENT_CONNECTION) where
ARGUMENT is dracut vlan argument or "" if the configuration does not apply
PARENT_CONNECTION is vlan parent connection of the connection
:rtype: tuple(str, NM.RemoteConnection)
"""
argument = ""
parent_con = None
if connection.get_connection_type() == "vlan":
setting_vlan = connection.get_setting_vlan()
parent_spec = setting_vlan.get_parent()
parent = None
# parent can be specified by connection uuid (eg from nm-c-e)
if len(parent_spec) == NM_CONNECTION_UUID_LENGTH:
parent_con = nm_client.get_connection_by_uuid(parent_spec)
if parent_con:
# On s390 with net.ifnames=0 there is no DEVICE so use NAME
parent = parent_con.get_interface_name() or parent_con.get_id()
# parent can be specified by interface
else:
parent = parent_spec
parent_cons = get_connections_available_for_iface(nm_client, parent)
if len(parent_cons) != 1:
log.error("unexpected number of connections found for vlan parent %s",
parent_spec)
if parent_cons:
parent_con = parent_cons[0]
if parent:
argument = "vlan={}:{}".format(iface, parent)
else:
log.error("can't find parent interface of vlan device %s specified by %s",
iface, parent_spec)
if not parent_con:
log.error("can't find parent connection of vlan device %s specified by %s",
iface, parent_spec)
return argument, parent_con
def _get_dracut_znet_argument_from_connection(connection):
"""Get dracut znet (s390) configuration for given NM connection.
:param connection: NetworkManager connection
:type connection: NM.RemoteConnection
:returns: dracut znet argument or "" if the configuration does not apply
:rtype: str
"""
argument = ""
wired_setting = connection.get_setting_wired()
if wired_setting and is_s390():
nettype = wired_setting.get_s390_nettype()
subchannels = wired_setting.get_s390_subchannels()
if nettype and subchannels:
argument = "rd.znet={},{}".format(nettype, subchannels)
options = wired_setting.get_property(NM.SETTING_WIRED_S390_OPTIONS)
if options:
options_string = ','.join("{}={}".format(key, val) for key, val in options.items())
argument += ",{}".format(options_string)
return argument
def get_slaves_from_connections(nm_client, slave_type, master_specs):
"""Get slaves of master of given type specified by uuid or interface.
:param nm_client: instance of NetworkManager client
:type nm_client: NM.Client
:param slave_type: type of the slave - NM setting "slave-type" value (eg. "team")
:type slave_type: str
:param master_specs: a list containing sepcification of master:
interface name or connection uuid or both
:type master_specs: list(str)
:returns: slaves specified by interface and connection uuid
:rtype: set((str,str))
"""
slaves = set()
for con in nm_client.get_connections():
if not con.get_setting_connection().get_slave_type() == slave_type:
continue
if con.get_setting_connection().get_master() in master_specs:
iface = get_iface_from_connection(nm_client, con.get_uuid())
if iface:
slaves.add((iface, con.get_uuid()))
return slaves