anaconda/anaconda-40.22.3.13/pyanaconda/modules/network/initialization.py
2024-11-14 21:39:56 -08:00

350 lines
15 KiB
Python

#
# Copyright (C) 2019 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 copy
import re
from pyanaconda.core.regexes import NM_MAC_INITRAMFS_CONNECTION
from pyanaconda.core.constants import NETWORK_CAPABILITY_TEAM
from pyanaconda.modules.common.task import Task
from pyanaconda.anaconda_loggers import get_module_logger
from pyanaconda.modules.network.network_interface import NetworkInitializationTaskInterface
from pyanaconda.modules.network.nm_client import get_device_name_from_network_data, \
update_connection_from_ksdata, add_connection_from_ksdata, bound_hwaddr_of_device, \
update_connection_values, commit_changes_with_autoconnection_blocked, \
get_config_file_connection_of_device, clone_connection_sync, nm_client_in_thread, \
is_bootif_connection
from pyanaconda.modules.network.device_configuration import supported_wired_device_types, \
virtual_device_types
from pyanaconda.modules.network.utils import guard_by_system_configuration, is_nbft_device
log = get_module_logger(__name__)
import gi
gi.require_version("NM", "1.0")
from gi.repository import NM
class ApplyKickstartTask(Task):
"""Task for application of kickstart network configuration."""
def __init__(self, network_data, supported_devices, capabilities,
bootif, ifname_option_values):
"""Create a new task.
:param network_data: kickstart network data to be applied
:type: list(NetworkData)
:param supported_devices: list of names of supported network devices
:type supported_devices: list(str)
:param capabilities: list of capabilities supported by the network backend
:type capabilities: list(int)
:param bootif: MAC addres of device to be used for --device=bootif specification
:type bootif: str
:param ifname_option_values: list of ifname boot option values
:type ifname_option_values: list(str)
"""
super().__init__()
self._network_data = network_data
self._supported_devices = supported_devices
self._capabilities = capabilities
self._bootif = bootif
self._ifname_option_values = ifname_option_values
@property
def name(self):
return "Apply kickstart"
def for_publication(self):
"""Return a DBus representation."""
return NetworkInitializationTaskInterface(self)
@guard_by_system_configuration(return_value=[])
def run(self):
"""Run the kickstart application.
:returns: names of devices to which kickstart was applied
:rtype: list(str)
"""
with nm_client_in_thread() as nm_client:
return self._run(nm_client)
def _run(self, nm_client):
applied_devices = []
if not self._network_data:
log.debug("%s: No kickstart data.", self.name)
return applied_devices
if not nm_client:
log.debug("%s: No NetworkManager available.", self.name)
return applied_devices
for network_data in self._network_data:
# Wireless is not supported
if network_data.essid:
log.info("%s: Wireless devices configuration is not supported.", self.name)
continue
if network_data.teamslaves and NETWORK_CAPABILITY_TEAM not in self._capabilities:
log.info("%s: Team devices configuration is not supported.", self.name)
continue
device_name = get_device_name_from_network_data(nm_client,
network_data,
self._supported_devices,
self._bootif)
if not device_name:
log.warning("%s: --device %s not found", self.name, network_data.device)
continue
if is_nbft_device(device_name):
log.debug("Ignoring nBFT device %s", device_name)
continue
applied_devices.append(device_name)
connection = self._find_initramfs_connection_of_iface(nm_client, device_name)
if connection:
# if the device was already configured in initramfs update the settings
log.debug("%s: updating connection %s of device %s",
self.name, connection.get_uuid(), device_name)
update_connection_from_ksdata(
nm_client,
connection,
network_data,
device_name,
ifname_option_values=self._ifname_option_values
)
if network_data.activate:
device = nm_client.get_device_by_iface(device_name)
nm_client.activate_connection_async(connection, device, None, None)
log.debug("%s: activating updated connection %s with device %s",
self.name, connection.get_uuid(), device_name)
else:
log.debug("%s: adding connection for %s", self.name, device_name)
add_connection_from_ksdata(
nm_client,
network_data,
device_name,
activate=network_data.activate,
ifname_option_values=self._ifname_option_values
)
return applied_devices
def _find_initramfs_connection_of_iface(self, nm_client, iface):
device = nm_client.get_device_by_iface(iface)
if device:
cons = device.get_available_connections()
for con in cons:
if con.get_interface_name() == iface and con.get_id() == iface:
return con
return None
class DumpMissingConfigFilesTask(Task):
"""Task for dumping of missing config files."""
def __init__(self, default_network_data, ifname_option_values):
"""Create a new task.
:param default_network_data: kickstart network data of default device configuration
:type default_network_data: NetworkData
:param ifname_option_values: list of ifname boot option values
:type ifname_option_values: list(str)
"""
super().__init__()
self._default_network_data = default_network_data
self._ifname_option_values = ifname_option_values
@property
def name(self):
return "Dump missing config files"
def for_publication(self):
"""Return a DBus representation."""
return NetworkInitializationTaskInterface(self)
def _select_persistent_connection_for_device(self, device, cons, allow_ports=False):
"""Select the connection suitable to store configuration for the device."""
iface = device.get_iface()
ac = device.get_active_connection()
if ac:
con = ac.get_connection()
if con.get_interface_name() == iface and con in cons:
if allow_ports or not con.get_setting_connection().get_master():
return con
else:
log.debug("%s: active connection for %s can't be used as persistent",
self.name, iface)
for con in cons:
if con.get_interface_name() == iface:
if allow_ports or not con.get_setting_connection().get_master():
return con
return None
def _update_connection(self, nm_client, con, iface):
log.debug("%s: updating id and binding (interface-name) of connection %s for %s",
self.name, con.get_uuid(), iface)
s_con = con.get_setting_connection()
s_con.set_property(NM.SETTING_CONNECTION_ID, iface)
s_con.set_property(NM.SETTING_CONNECTION_INTERFACE_NAME, iface)
s_wired = con.get_setting_wired()
if s_wired:
# By default connections are bound to interface name
s_wired.set_property(NM.SETTING_WIRED_MAC_ADDRESS, None)
bound_mac = bound_hwaddr_of_device(nm_client, iface, self._ifname_option_values)
if bound_mac:
s_wired.set_property(NM.SETTING_WIRED_MAC_ADDRESS, bound_mac)
log.debug("%s: iface %s bound to mac address %s by ifname boot option",
self.name, iface, bound_mac)
log.debug("%s: updating addr-gen-mode of connection %s for %s",
self.name, con.get_uuid(), iface)
s_ipv6 = con.get_setting_ip6_config()
# For example port connections do not have ipv6 setting present
if s_ipv6:
s_ipv6.set_property(NM.SETTING_IP6_CONFIG_ADDR_GEN_MODE,
NM.SettingIP6ConfigAddrGenMode.EUI64)
@guard_by_system_configuration(return_value=[])
def run(self):
"""Run dumping of missing config files.
:returns: names of devices for which config file was created
:rtype: list(str)
"""
with nm_client_in_thread() as nm_client:
return self._run(nm_client)
def _run(self, nm_client):
new_configs = []
if not nm_client:
log.debug("%s: No NetworkManager available.", self.name)
return new_configs
dumped_device_types = supported_wired_device_types + virtual_device_types
for device in nm_client.get_devices():
if device.get_device_type() not in dumped_device_types:
continue
iface = device.get_iface()
if is_nbft_device(iface or ""):
log.debug("Ignoring nBFT device %s", iface)
continue
if get_config_file_connection_of_device(nm_client, iface):
continue
available_cons = device.get_available_connections()
log.debug("%s: %s connections found for device %s", self.name,
[con.get_uuid() for con in available_cons], iface)
initramfs_cons = [con for con in available_cons
if self._is_initramfs_connection(con, iface)]
log.debug("%s: %s initramfs connections found for device %s", self.name,
[con.get_uuid() for con in initramfs_cons], iface)
dumped_con = None
device_is_port = any(con.get_setting_connection().get_master()
for con in available_cons)
if device_is_port:
# We have to dump persistent ifcfg files for ports created in initramfs
# Filter out potenital connection created for BOOTIF option rhbz#2175664
port_cons = [c for c in available_cons if not is_bootif_connection(c)]
if initramfs_cons:
if len(port_cons) == 1:
log.debug("%s: port device %s has an initramfs port connection",
self.name, iface)
dumped_con = self._select_persistent_connection_for_device(
device, port_cons, allow_ports=True)
else:
log.debug("%s: port device %s has an initramfs connection",
self.name, iface)
else:
log.debug("%s: not creating default connection for port device %s",
self.name, iface)
continue
if not dumped_con:
dumped_con = self._select_persistent_connection_for_device(device, available_cons)
if not dumped_con and len(initramfs_cons) == 1:
# Try to clone the persistent connection for the device
# from the connection which should be a generic (not bound
# to iface) connection created by NM in initramfs
dumped_con = clone_connection_sync(nm_client, initramfs_cons[0], con_id=iface)
if dumped_con:
log.debug("%s: dumping connection %s to config file for %s",
self.name, dumped_con.get_uuid(), iface)
self._dump_connection(nm_client, dumped_con, iface, bool(initramfs_cons))
else:
log.debug("%s: none of the connections can be dumped as persistent",
self.name)
if len(available_cons) > 1 and not device_is_port:
log.warning("%s: unexpected number of connections, not dumping any",
self.name)
continue
log.debug("%s: creating default connection for %s", self.name, iface)
self._create_default_connection(nm_client, iface, bool(initramfs_cons))
new_configs.append(iface)
return new_configs
def _dump_connection(self, nm_client, dumped_con, iface, initramfs_con):
self._update_connection(nm_client, dumped_con, iface)
# Update some values of connection generated in initramfs so it
# can be used as persistent configuration.
if initramfs_con:
update_connection_values(
dumped_con,
[
# Make sure ONBOOT is yes
(NM.SETTING_CONNECTION_SETTING_NAME,
NM.SETTING_CONNECTION_AUTOCONNECT,
True),
# Update cloned generic connection from initramfs
(NM.SETTING_CONNECTION_SETTING_NAME,
NM.SETTING_CONNECTION_MULTI_CONNECT,
0),
# Update cloned generic connection from initramfs
(NM.SETTING_CONNECTION_SETTING_NAME,
NM.SETTING_CONNECTION_WAIT_DEVICE_TIMEOUT,
-1)
]
)
commit_changes_with_autoconnection_blocked(dumped_con, nm_client)
def _create_default_connection(self, nm_client, iface, initramfs_con):
network_data = copy.deepcopy(self._default_network_data)
network_data.onboot = initramfs_con
add_connection_from_ksdata(
nm_client,
network_data,
iface,
activate=False,
ifname_option_values=self._ifname_option_values
)
def _is_initramfs_connection(self, con, iface):
con_id = con.get_id()
return con_id in ["Wired Connection", iface] \
or re.match(NM_MAC_INITRAMFS_CONNECTION, con_id)