class NetworkService(KickstartService): """The Network service.""" def __init__(self): super().__init__() self._firewall_module = FirewallModule() self.hostname_changed = Signal() self._hostname = "localhost.localdomain" self.current_hostname_changed = Signal() self._hostname_service_proxy = None if conf.system.provides_system_bus: self._hostname_service_proxy = HOSTNAME.get_proxy() self._hostname_service_proxy.PropertiesChanged.connect( self._hostname_service_properties_changed ) self.connected_changed = Signal() self.nm_client = None # TODO fallback solution - use Gio/GNetworkMonitor ? if SystemBus.check_connection(): nm_client = NM.Client.new(None) if nm_client.get_nm_running(): self.nm_client = nm_client self.nm_client.connect("notify::%s" % NM.CLIENT_STATE, self._nm_state_changed) initial_state = self.nm_client.get_state() self.set_connected(self._nm_state_connected(initial_state)) else: log.debug("NetworkManager is not running.") self._original_network_data = [] self._device_configurations = None self._use_device_configurations = False self.configurations_changed = Signal() self._default_device_specification = DEFAULT_DEVICE_SPECIFICATION self._bootif = None self._ifname_option_values = [] self._disable_ipv6 = False self._apply_boot_options(kernel_arguments) def publish(self): """Publish the module.""" TaskContainer.set_namespace(NETWORK.namespace) self._firewall_module.publish() DBus.publish_object(NETWORK.object_path, NetworkInterface(self)) DBus.register_service(NETWORK.service_name) @property def kickstart_specification(self): """Return the kickstart specification.""" return NetworkKickstartSpecification @property def default_device_specification(self): """Get the default specification for missing kickstart --device option.""" return self._default_device_specification @default_device_specification.setter def default_device_specification(self, specification): """Set the default specification for missing kickstart --device option. :param specification: device specification accepted by network --device option :type specification: str """ self._default_device_specification = specification log.debug("default kickstart device specification set to %s", specification) def process_kickstart(self, data): """Process the kickstart data.""" # Handle default value for --device spec = self.default_device_specification if update_network_data_with_default_device(data.network.network, spec): log.debug("used '%s' for missing network --device options", spec) if update_first_network_command_activate_value(data.network.network): log.debug("updated activate value of the first network command (None -> True)") self._original_network_data = data.network.network if data.network.hostname: self.set_hostname(data.network.hostname) self._firewall_module.process_kickstart(data) def setup_kickstart(self, data): """Set up the kickstart data.""" if self._device_configurations and self._use_device_configurations: log.debug("using device configurations to generate kickstart") device_data = self.generate_kickstart_network_data(data.NetworkData) else: log.debug("using original kickstart data to generate kickstart") device_data = self._original_network_data data.network.network = device_data hostname_data = data.NetworkData(hostname=self.hostname, bootProto="") update_network_hostname_data(data.network.network, hostname_data) # firewall self._firewall_module.setup_kickstart(data) return data def _is_device_activated(self, iface): device = self.nm_client.get_device_by_iface(iface) return device and device.get_state() == NM.DeviceState.ACTIVATED def generate_kickstart_network_data(self, network_data_class): rv = [] for cfg in self._device_configurations.get_all(): network_data = None if cfg.device_type != NM.DeviceType.WIFI and cfg.connection_uuid: ifcfg = get_ifcfg_file([("UUID", cfg.connection_uuid)]) if not ifcfg: log.debug("Ifcfg file for %s not found.") continue network_data = get_kickstart_network_data(ifcfg, self.nm_client, network_data_class) if not network_data: log.debug("Device configuration %s does not generate any kickstart data", cfg) continue if cfg.device_name: if self._is_device_activated(cfg.device_name): network_data.activate = True else: # First network command defaults to --activate so we must # use --no-activate explicitly to prevent the default # (Default value is None) if not rv: network_data.activate = False rv.append(network_data) return rv @property def hostname(self): """Return the hostname.""" return self._hostname def set_hostname(self, hostname): """Set the hostname.""" self._hostname = hostname self.hostname_changed.emit() log.debug("Hostname is set to %s", hostname) def _hostname_service_properties_changed(self, interface, changed, invalid): if interface == HOSTNAME.interface_name and "Hostname" in changed: hostname = changed["Hostname"].unpack() self.current_hostname_changed.emit(hostname) log.debug("Current hostname changed to %s", hostname) def get_current_hostname(self): """Return current hostname of the system.""" if self._hostname_service_proxy: return self._hostname_service_proxy.Hostname log.debug("Current hostname cannot be get.") return "" def set_current_hostname(self, hostname): """Set current system hostname.""" if not self._hostname_service_proxy: log.debug("Current hostname cannot be set.") return self._hostname_service_proxy.SetHostname(hostname, False) log.debug("Current hostname is set to %s", hostname) @property def nm_available(self): return self.nm_client is not None @property def connected(self): """Is the system connected to the network?""" if self.nm_available: return self._connected else: log.debug("Connectivity state can't be determined, assuming connected.") return True def set_connected(self, connected): """Set network connectivity status.""" self._connected = connected self.connected_changed.emit() self.module_properties_changed.emit() log.debug("Connected to network: %s", connected) def is_connecting(self): """Is NM in connecting state?""" if self.nm_available: return self.nm_client.get_state() == NM.State.CONNECTING else: log.debug("Connectivity state can't be determined, assuming not connecting.") return False @staticmethod def _nm_state_connected(state): return state in (NM.State.CONNECTED_LOCAL, NM.State.CONNECTED_SITE, NM.State.CONNECTED_GLOBAL) def _nm_state_changed(self, *args): state = self.nm_client.get_state() log.debug("NeworkManager state changed to %s", state) self.set_connected(self._nm_state_connected(state)) @property def disable_ipv6(self): """Disable IPv6 on target system.""" return self._disable_ipv6 @disable_ipv6.setter def disable_ipv6(self, disable_ipv6): """Set disable IPv6 on target system. :param disable_ipv6: should ipv6 be disabled on target system :type disable_ipv6: bool """ self._disable_ipv6 = disable_ipv6 log.debug("disable IPv6 is set to %s", disable_ipv6) def collect_requirements(self): """Return installation requirements for this module. :return: a list of requirements """ # first collect requirements from the Firewall sub-module requirements = self._firewall_module.collect_requirements() # team device configuration support if self.get_team_devices(): requirements.append(Requirement.for_package( "teamd", reason="Necessary for network team device configuration." )) # prefixdevname if self._is_using_persistent_device_names(kernel_arguments): requirements.append(Requirement.for_package( "prefixdevname", reason="Necessary for persistent network device naming feature." )) return requirements def configure_activation_on_boot_with_task(self, onboot_ifaces): """Configure automatic activation of devices on system boot. 1) Specified devices are set to be activated automatically. 2) Policy set in anaconda configuration (default_on_boot) is applied. :param onboot_ifaces: list of network interfaces which should have ONBOOT=yes """ onboot_ifaces_by_policy = [] if self._should_apply_onboot_policy() and \ not self._has_any_onboot_yes_device(self._device_configurations): onboot_ifaces_by_policy = self._get_onboot_ifaces_by_policy( conf.network.default_on_boot ) log.debug("Configure ONBOOT: set to yes for %s (reqested) %s (policy)", onboot_ifaces, onboot_ifaces_by_policy) all_onboot_ifaces = list(set(onboot_ifaces + onboot_ifaces_by_policy)) task = ConfigureActivationOnBootTask( self.nm_client, all_onboot_ifaces ) task.succeeded_signal.connect(lambda: self.log_task_result(task)) return task def install_network_with_task(self, overwrite): """Install network with an installation task. :param overwrite: overwrite existing configuration :return: a DBus path of an installation task """ disable_ipv6 = self.disable_ipv6 and devices_ignore_ipv6(self.nm_client, supported_wired_device_types) network_ifaces = [device.get_iface() for device in self.nm_client.get_devices()] task = NetworkInstallationTask( conf.target.system_root, disable_ipv6, overwrite, network_ifaces, self.ifname_option_values, self._is_using_persistent_device_names(kernel_arguments) ) task.succeeded_signal.connect( lambda: self.log_task_result(task, root_path=conf.target.system_root) ) return task def configure_hostname_with_task(self, overwrite): """Configure hostname with an installation task. :param overwrite: overwrite existing configuration :return: a DBus path of an installation task """ return HostnameConfigurationTask( conf.target.system_root, self.hostname, overwrite ) def _should_apply_onboot_policy(self): """Should policy for ONBOOT of devices be applied?.""" # Not if any network device was configured via kickstart. if self._original_network_data: return False # Not if any network device was configured in UI. if self._use_device_configurations: return False # Not if there is no configuration to apply the policy to if not self._device_configurations or not self._device_configurations.get_all(): return False return True def _has_any_onboot_yes_device(self, device_configurations): """Does any device have ONBOOT value set to 'yes'?""" uuids = [dev_cfg.connection_uuid for dev_cfg in device_configurations.get_all() if dev_cfg.connection_uuid] for uuid in uuids: con = self.nm_client.get_connection_by_uuid(uuid) if con: if (con.get_flags() & NM.SettingsConnectionFlags.UNSAVED): log.debug("ONBOOT policy: not considering UNSAVED connection %s", con.get_uuid()) continue if con.get_setting_connection().get_autoconnect(): log.debug("ONBOOT policy: %s has 'autoconnect' == True", con.get_uuid()) return True return False def _get_onboot_ifaces_by_policy(self, policy): """Get network interfaces that shoud have ONBOOT set to 'yes' by policy.""" ifaces = [] if policy is NetworkOnBoot.FIRST_WIRED_WITH_LINK: # choose first device having link log.info("Onboot policy: choosing the first device having link.") for device in self.nm_client.get_devices(): if device.get_device_type() not in supported_device_types: continue if device.get_device_type() == NM.DeviceType.WIFI: continue if device.get_carrier(): ifaces.append(device.get_iface()) break elif policy is NetworkOnBoot.DEFAULT_ROUTE_DEVICE: # choose the device used during installation # (ie for majority of cases the one having the default route) log.info("Onboot policy: choosing the default route device.") iface = get_default_route_iface() or get_default_route_iface(family="inet6") if iface: device = self.nm_client.get_device_by_iface(iface) if device.get_device_type() != NM.DeviceType.WIFI: ifaces.append(iface) return ifaces def create_device_configurations(self): """Create and populate the state of network devices configuration.""" if not self.nm_available: log.debug("Device configurations can't be created, no NetworkManager available.") return self._device_configurations = DeviceConfigurations(self.nm_client) self._device_configurations.configurations_changed.connect( self.device_configurations_changed_cb ) self._device_configurations.reload() self._device_configurations.connect() log.debug("Device configurations created: %s", self._device_configurations) def get_device_configurations(self): if not self._device_configurations: return [] return self._device_configurations.get_all() def device_configurations_changed_cb(self, changes): log.debug("Device configurations changed: %s", changes) self.configurations_changed.emit(changes) def consolidate_initramfs_connections_with_task(self): """Ensure devices configured in initramfs have no more than one NM connection. In case of multiple connections for device having ifcfg configuration from boot options, the connection should correspond to the ifcfg file. NetworkManager can be generating additional in-memory connection in case it fails to match device configuration to the ifcfg (#1433891). By reactivating the device with ifcfg connection the generated in-memory connection will be deleted by NM. Don't enforce on slave devices for which having multiple connections can be valid (slave connection, regular device connection). :returns: a task consolidating the connections """ task = ConsolidateInitramfsConnectionsTask(self.nm_client) task.succeeded_signal.connect(lambda: self.log_task_result(task, check_result=True)) return task def get_supported_devices(self): """Get information about existing supported devices on the system. :return: list of objects describing found supported devices :rtype: list(NetworkDeviceInfo) """ # TODO guard on system (provides_system_bus) supported_devices = [] if not self.nm_available: log.debug("Supported devices can't be determined.") return supported_devices for device in self.nm_client.get_devices(): if device.get_device_type() not in supported_device_types: continue dev_info = NetworkDeviceInfo() dev_info.set_from_nm_device(device) if not all((dev_info.device_name, dev_info.device_type, dev_info.hw_address)): log.warning("Missing value when setting NetworkDeviceInfo from NM device: %s", dev_info) supported_devices.append(dev_info) return supported_devices def get_activated_interfaces(self): """Get activated network interfaces. Device is considered as activated if it has an active network (NM) connection. :return: list of names of devices having active network connection :rtype: list(str) """ # TODO guard on system (provides_system_bus) activated_ifaces = [] if not self.nm_available: log.debug("Activated interfaces can't be determined.") return activated_ifaces for ac in self.nm_client.get_active_connections(): if ac.get_state() != NM.ActiveConnectionState.ACTIVATED: continue for device in ac.get_devices(): activated_ifaces.append(device.get_ip_iface() or device.get_iface()) return activated_ifaces def get_team_devices(self): """Get existing team network devices. :return: basic information about existing team devices :rtype: list(NetworkDeviceInfo) """ return [dev for dev in self.get_supported_devices() if dev.device_type == NM.DeviceType.TEAM] @property def bootif(self): """Get the value of kickstart --device bootif option.""" return self._bootif @bootif.setter def bootif(self, specification): """Set the value of kickstart --device bootif option. :param specification: mac address specified by kickstart --device bootif option :type specification: str """ self._bootif = specification log.debug("bootif device specification is set to %s", specification) @property def ifname_option_values(self): """Get values of ifname boot option.""" return self._ifname_option_values @ifname_option_values.setter def ifname_option_values(self, values): """Set values of ifname boot option. :param values: list of ifname boot option values :type values: list(str) """ self._ifname_option_values = values log.debug("ifname boot option values are set to %s", values) def apply_kickstart_with_task(self): """Apply kickstart configuration which has not already been applied. * Activate configurations created in initramfs if --activate is True. * Create configurations for %pre kickstart commands and activate eventually. :returns: a task applying the kickstart """ supported_devices = [dev_info.device_name for dev_info in self.get_supported_devices()] task = ApplyKickstartTask(self.nm_client, self._original_network_data, supported_devices, self.bootif, self.ifname_option_values) task.succeeded_signal.connect(lambda: self.log_task_result(task, check_result=True)) return task def set_real_onboot_values_from_kickstart_with_task(self): """Update ifcfg ONBOOT values according to kickstart configuration. So it reflects the --onboot option. This is needed because: 1) For ifcfg files created in initramfs we use ONBOOT for --activate 2) For kickstart applied in stage 2 we can't set the autoconnect setting of connection because the device would be activated immediately. :returns: a task setting the values """ supported_devices = [dev_info.device_name for dev_info in self.get_supported_devices()] task = SetRealOnbootValuesFromKickstartTask(self.nm_client, self._original_network_data, supported_devices, self.bootif, self.ifname_option_values) task.succeeded_signal.connect(lambda: self.log_task_result(task, check_result=True)) return task def dump_missing_ifcfg_files_with_task(self): """Dump missing default ifcfg file for wired devices. Make sure each supported wired device has ifcfg file. For default auto connections created by NM upon start (which happens in case of missing ifcfg file, eg the file was not created in initramfs) rename the in-memory connection using device name and dump it into ifcfg file. If default auto connections are turned off by NM configuration (based on policy, eg on RHEL or server), the connection will be created by Anaconda and dumped into ifcfg file. The connection id (and consequently ifcfg file name) is set to device name. :returns: a task dumping the files """ data = self.get_kickstart_handler() default_network_data = data.NetworkData(onboot=False, ipv6="auto") task = DumpMissingIfcfgFilesTask(self.nm_client, default_network_data, self.ifname_option_values) task.succeeded_signal.connect(lambda: self.log_task_result(task, check_result=True)) return task def network_device_configuration_changed(self): if not self._device_configurations: log.error("Got request to use DeviceConfigurations that has not been created yet") self._use_device_configurations = True def get_dracut_arguments(self, iface, target_ip, hostname, ibft): """Get dracut arguments for the iface and iSCSI target. The dracut arguments would activate the iface in initramfs so that the iSCSI target can be attached (for example to mount root filesystem). :param iface: network interface used to connect to the target :param target_ip: IP of the iSCSI target :param hostname: static hostname to be configured :param ibft: the device should be configured from iBFT """ log.debug("Getting dracut arguments for iface %s target %s (ibft==%s)", iface, target_ip, ibft) dracut_args = [] if not self.nm_available: log.debug("Get dracut arguments: can't be obtained, no NetworkManager available.") return dracut_args if iface and iface not in (device.get_iface() for device in self.nm_client.get_devices()): log.error("Get dracut arguments for %s: device not found", iface) return dracut_args connections = self.nm_client.get_connections() target_connections = [] if ibft: target_connections = [con for con in connections if is_ibft_connection(con)] else: for cfg in self._device_configurations.get_for_device(iface): uuid = cfg.connection_uuid if uuid: connection = self.nm_client.get_connection_by_uuid(uuid) if connection: target_connections.append(connection) if target_connections: if len(target_connections) > 1: log.debug("Get dracut arguments: " "multiple connections found for traget %s: %s, taking the first one", [con.get_uuid() for con in target_connections], target_ip) connection = target_connections[0] else: log.error("Get dracut arguments: can't find connection for target %s", target_ip) return dracut_args dracut_args = list(get_dracut_arguments_from_connection( self.nm_client, connection, iface, target_ip, hostname, ibft )) return dracut_args def _apply_boot_options(self, kernel_args): """Apply boot options to the module. :param kernel_args: structure holding installer boot options :type kernel_args: KernelArguments """ log.debug("Applying boot options %s", kernel_args) if 'ksdevice' in kernel_args: self.default_device_specification = kernel_args.get('ksdevice') if 'BOOTIF' in kernel_args: self.bootif = kernel_args.get('BOOTIF')[3:].replace("-", ":").upper() if 'ifname' in kernel_args: self.ifname_option_values = kernel_args.get("ifname").split() if 'noipv6' in kernel_args: self.disable_ipv6 = True def log_task_result(self, task, check_result=False, root_path=""): if not check_result: self.log_configuration_state(task.name, root_path) else: result = task.get_result() log.debug("%s result: %s", task.name, result) if result: self.log_configuration_state(task.name, root_path) def log_configuration_state(self, msg_header, root_path=""): """Log the current network configuration state. Logs ifcfg files and NM connections """ log.debug("Dumping configuration state - %s", msg_header) for line in get_ifcfg_files_content(root_path=root_path).splitlines(): log.debug(line) if self.nm_available: for line in get_connections_dump(self.nm_client).splitlines(): log.debug(line) def _is_using_persistent_device_names(self, kernel_args): return 'net.ifnames.prefix' in kernel_args
class NetworkModule(KickstartModule): """The Network module.""" def __init__(self): super().__init__() self._firewall_module = FirewallModule() self.hostname_changed = Signal() self._hostname = "localhost.localdomain" self.current_hostname_changed = Signal() self._hostname_service_proxy = None if conf.system.provides_system_bus: self._hostname_service_proxy = HOSTNAME.get_proxy() self._hostname_service_proxy.PropertiesChanged.connect(self._hostname_service_properties_changed) self.connected_changed = Signal() self.nm_client = None # TODO fallback solution - use Gio/GNetworkMonitor ? if SystemBus.check_connection(): nm_client = NM.Client.new(None) if nm_client.get_nm_running(): self.nm_client = nm_client self.nm_client.connect("notify::%s" % NM.CLIENT_STATE, self._nm_state_changed) initial_state = self.nm_client.get_state() self.set_connected(self._nm_state_connected(initial_state)) else: log.debug("NetworkManager is not running.") def publish(self): """Publish the module.""" self._firewall_module.publish() DBus.publish_object(NETWORK.object_path, NetworkInterface(self)) DBus.register_service(NETWORK.service_name) @property def kickstart_specification(self): """Return the kickstart specification.""" return NetworkKickstartSpecification def process_kickstart(self, data): """Process the kickstart data.""" if data.network.hostname: self.set_hostname(data.network.hostname) self._firewall_module.process_kickstart(data) def generate_kickstart(self): """Return the kickstart string.""" data = self.get_kickstart_handler() data.network.network = [] # hostname hostname_data = data.NetworkData(hostname=self.hostname, bootProto="") data.network.network.append(hostname_data) # firewall self._firewall_module.setup_kickstart(data) return str(data) @property def hostname(self): """Return the hostname.""" return self._hostname def set_hostname(self, hostname): """Set the hostname.""" self._hostname = hostname self.hostname_changed.emit() log.debug("Hostname is set to %s", hostname) def _hostname_service_properties_changed(self, interface, changed, invalid): if interface == HOSTNAME.interface_name and "Hostname" in changed: hostname = changed["Hostname"] self.current_hostname_changed.emit(hostname) log.debug("Current hostname changed to %s", hostname) def get_current_hostname(self): """Return current hostname of the system.""" if self._hostname_service_proxy: return self._hostname_service_proxy.Hostname log.debug("Current hostname cannot be get.") return "" def set_current_hostname(self, hostname): """Set current system hostname.""" if not self._hostname_service_proxy: log.debug("Current hostname cannot be set.") return self._hostname_service_proxy.SetHostname(hostname, False) log.debug("Current hostname is set to %s", hostname) @property def nm_available(self): return self.nm_client is not None @property def connected(self): """Is the system connected to the network?""" if self.nm_available: return self._connected else: log.debug("Connectivity state can't be determined, assuming connected.") return True def set_connected(self, connected): """Set network connectivity status.""" self._connected = connected self.connected_changed.emit() self.module_properties_changed.emit() log.debug("Connected to network: %s", connected) def is_connecting(self): """Is NM in connecting state?""" if self.nm_available: return self.nm_client.get_state() == NM.State.CONNECTING else: log.debug("Connectivity state can't be determined, assuming not connecting.") return False @staticmethod def _nm_state_connected(state): return state in (NM.State.CONNECTED_LOCAL, NM.State.CONNECTED_SITE, NM.State.CONNECTED_GLOBAL) def _nm_state_changed(self, *args): state = self.nm_client.get_state() log.debug("NeworkManager state changed to %s", state) self.set_connected(self._nm_state_connected(state))
class NetworkModule(KickstartModule): """The Network module.""" def __init__(self): super().__init__() self._firewall_module = FirewallModule() self.hostname_changed = Signal() self._hostname = "localhost.localdomain" self.current_hostname_changed = Signal() self._hostname_service_proxy = None if SystemBus.check_connection(): self._hostname_service_proxy = HOSTNAME.get_proxy() self._hostname_service_proxy.PropertiesChanged.connect( self._hostname_service_properties_changed) self.connected_changed = Signal() self.nm_client = None # TODO fallback solution - use Gio/GNetworkMonitor ? if SystemBus.check_connection(): nm_client = NM.Client.new(None) if nm_client.get_nm_running(): self.nm_client = nm_client self.nm_client.connect("notify::%s" % NM.CLIENT_STATE, self._nm_state_changed) initial_state = self.nm_client.get_state() self.set_connected(self._nm_state_connected(initial_state)) else: log.debug("NetworkManager is not running.") def publish(self): """Publish the module.""" self._firewall_module.publish() DBus.publish_object(NETWORK.object_path, NetworkInterface(self)) DBus.register_service(NETWORK.service_name) @property def kickstart_specification(self): """Return the kickstart specification.""" return NetworkKickstartSpecification def process_kickstart(self, data): """Process the kickstart data.""" if data.network.hostname: self.set_hostname(data.network.hostname) self._firewall_module.process_kickstart(data) def generate_kickstart(self): """Return the kickstart string.""" data = self.get_kickstart_handler() data.network.network = [] # hostname hostname_data = data.NetworkData(hostname=self.hostname, bootProto="") data.network.network.append(hostname_data) # firewall self._firewall_module.setup_kickstart(data) return str(data) @property def hostname(self): """Return the hostname.""" return self._hostname def set_hostname(self, hostname): """Set the hostname.""" self._hostname = hostname self.hostname_changed.emit() log.debug("Hostname is set to %s", hostname) def _hostname_service_properties_changed(self, interface, changed, invalid): if interface == HOSTNAME.interface_name and "Hostname" in changed: hostname = changed["Hostname"] self.current_hostname_changed.emit(hostname) log.debug("Current hostname changed to %s", hostname) def get_current_hostname(self): """Return current hostname of the system.""" if self._hostname_service_proxy: return self._hostname_service_proxy.Hostname log.debug("Current hostname cannot be get.") return "" def set_current_hostname(self, hostname): """Set current system hostname.""" if not self._hostname_service_proxy: log.debug("Current hostname cannot be set.") return self._hostname_service_proxy.SetHostname(hostname, False) log.debug("Current hostname is set to %s", hostname) @property def nm_available(self): return self.nm_client is not None @property def connected(self): """Is the system connected to the network?""" if self.nm_available: return self._connected else: log.debug( "Connectivity state can't be determined, assuming connected.") return True def set_connected(self, connected): """Set network connectivity status.""" self._connected = connected self.connected_changed.emit() self.module_properties_changed.emit() log.debug("Connected to network: %s", connected) def is_connecting(self): """Is NM in connecting state?""" if self.nm_available: return self.nm_client.get_state() == NM.State.CONNECTING else: log.debug( "Connectivity state can't be determined, assuming not connecting." ) return False @staticmethod def _nm_state_connected(state): return state in (NM.State.CONNECTED_LOCAL, NM.State.CONNECTED_SITE, NM.State.CONNECTED_GLOBAL) def _nm_state_changed(self, *args): state = self.nm_client.get_state() log.debug("NeworkManager state changed to %s", state) self.set_connected(self._nm_state_connected(state))
class NetworkModule(KickstartModule): """The Network module.""" def __init__(self): super().__init__() self._firewall_module = FirewallModule() self.hostname_changed = Signal() self._hostname = "localhost.localdomain" self.disable_ipv6_changed = Signal() self._disable_ipv6 = False self.current_hostname_changed = Signal() self._hostname_service_proxy = None if conf.system.provides_system_bus: self._hostname_service_proxy = HOSTNAME.get_proxy() self._hostname_service_proxy.PropertiesChanged.connect( self._hostname_service_properties_changed) self.connected_changed = Signal() self.nm_client = None # TODO fallback solution - use Gio/GNetworkMonitor ? if SystemBus.check_connection(): nm_client = NM.Client.new(None) if nm_client.get_nm_running(): self.nm_client = nm_client self.nm_client.connect("notify::%s" % NM.CLIENT_STATE, self._nm_state_changed) initial_state = self.nm_client.get_state() self.set_connected(self._nm_state_connected(initial_state)) else: log.debug("NetworkManager is not running.") self._original_network_data = [] self._onboot_yes_ifaces = [] self._device_configurations = None self._use_device_configurations = False self.configurations_changed = Signal() self._default_device_specification = DEFAULT_DEVICE_SPECIFICATION self._bootif = None self._ifname_option_values = [] def publish(self): """Publish the module.""" self._firewall_module.publish() DBus.publish_object(NETWORK.object_path, NetworkInterface(self)) DBus.register_service(NETWORK.service_name) @property def kickstart_specification(self): """Return the kickstart specification.""" return NetworkKickstartSpecification @property def default_device_specification(self): """Get the default specification for missing kickstart --device option.""" return self._default_device_specification @default_device_specification.setter def default_device_specification(self, specification): """Set the default specification for missing kickstart --device option. :param specifiacation: device specification accepted by network --device option :type specification: str """ self._default_device_specification = specification log.debug("default kickstart device specification set to %s", specification) def process_kickstart(self, data): """Process the kickstart data.""" log.debug("Processing kickstart data...") # Handle default value for --device spec = self.default_device_specification if update_network_data_with_default_device(data.network.network, spec): log.debug("used '%s' for missing network --device options", spec) if update_first_network_command_activate_value(data.network.network): log.debug( "updated activate value of the first network command (None -> True)" ) self._original_network_data = data.network.network if data.network.hostname: self.set_hostname(data.network.hostname) self._firewall_module.process_kickstart(data) def generate_kickstart(self): """Return the kickstart string.""" data = self.get_kickstart_handler() if self._device_configurations and self._use_device_configurations: log.debug("using device configurations to generate kickstart") device_data = self.generate_kickstart_network_data( data.NetworkData) else: log.debug("using original kickstart data to generate kickstart") device_data = self._original_network_data data.network.network = device_data self._update_network_data_with_onboot(data.network.network, self._onboot_yes_ifaces) hostname_data = data.NetworkData(hostname=self.hostname, bootProto="") update_network_hostname_data(data.network.network, hostname_data) # firewall self._firewall_module.setup_kickstart(data) return str(data) def _is_device_activated(self, iface): device = self.nm_client.get_device_by_iface(iface) return device and device.get_state() == NM.DeviceState.ACTIVATED def generate_kickstart_network_data(self, network_data_class): rv = [] for cfg in self._device_configurations.get_all(): network_data = None if cfg.device_type != NM.DeviceType.WIFI and cfg.connection_uuid: ifcfg = get_ifcfg_file([("UUID", cfg.connection_uuid)]) if not ifcfg: log.debug("Ifcfg file for %s not found.") continue network_data = get_kickstart_network_data( ifcfg, self.nm_client, network_data_class) if not network_data: log.debug( "Device configuration %s does not generate any kickstart data", cfg) continue if cfg.device_name: if self._is_device_activated(cfg.device_name): network_data.activate = True else: # First network command defaults to --activate so we must # use --no-activate explicitly to prevent the default # (Default value is None) if not rv: network_data.activate = False rv.append(network_data) return rv @property def hostname(self): """Return the hostname.""" return self._hostname def set_hostname(self, hostname): """Set the hostname.""" self._hostname = hostname self.hostname_changed.emit() log.debug("Hostname is set to %s", hostname) def _hostname_service_properties_changed(self, interface, changed, invalid): if interface == HOSTNAME.interface_name and "Hostname" in changed: hostname = changed["Hostname"] self.current_hostname_changed.emit(hostname) log.debug("Current hostname changed to %s", hostname) def get_current_hostname(self): """Return current hostname of the system.""" if self._hostname_service_proxy: return self._hostname_service_proxy.Hostname log.debug("Current hostname cannot be get.") return "" def set_current_hostname(self, hostname): """Set current system hostname.""" if not self._hostname_service_proxy: log.debug("Current hostname cannot be set.") return self._hostname_service_proxy.SetHostname(hostname, False) log.debug("Current hostname is set to %s", hostname) @property def nm_available(self): return self.nm_client is not None @property def connected(self): """Is the system connected to the network?""" if self.nm_available: return self._connected else: log.debug( "Connectivity state can't be determined, assuming connected.") return True def set_connected(self, connected): """Set network connectivity status.""" self._connected = connected self.connected_changed.emit() self.module_properties_changed.emit() log.debug("Connected to network: %s", connected) def is_connecting(self): """Is NM in connecting state?""" if self.nm_available: return self.nm_client.get_state() == NM.State.CONNECTING else: log.debug( "Connectivity state can't be determined, assuming not connecting." ) return False @staticmethod def _nm_state_connected(state): return state in (NM.State.CONNECTED_LOCAL, NM.State.CONNECTED_SITE, NM.State.CONNECTED_GLOBAL) def _nm_state_changed(self, *args): state = self.nm_client.get_state() log.debug("NeworkManager state changed to %s", state) self.set_connected(self._nm_state_connected(state)) @property def disable_ipv6(self): """Disable IPv6 on target system.""" return self._disable_ipv6 def set_disable_ipv6(self, disable_ipv6): """Set disable IPv6 on target system.""" self._disable_ipv6 = disable_ipv6 self.disable_ipv6_changed.emit() log.debug("Disable IPv6 is set to %s", disable_ipv6) def install_network_with_task(self, sysroot, onboot_ifaces, overwrite): """Install network with an installation task. :param sysroot: a path to the root of the installed system :param onboot_ifaces: list of network interfaces which should have ONBOOT=yes :param overwrite: overwrite existing configuration :return: a DBus path of an installation task """ disable_ipv6 = self.disable_ipv6 and devices_ignore_ipv6( self.nm_client, supported_wired_device_types) network_ifaces = [ device.get_iface() for device in self.nm_client.get_devices() ] onboot_ifaces_by_policy = [] if self._should_apply_onboot_policy() and \ not self._has_any_onboot_yes_device(self._device_configurations): onboot_ifaces_by_policy = self._get_onboot_ifaces_by_policy( conf.network.default_on_boot) all_onboot_ifaces = list(set(onboot_ifaces + onboot_ifaces_by_policy)) self._onboot_yes_ifaces = all_onboot_ifaces onboot_yes_uuids = [ find_ifcfg_uuid_of_device(self.nm_client, iface) or "" for iface in all_onboot_ifaces ] log.debug("ONBOOT will be set to yes for %s (fcoe) %s (policy)", onboot_ifaces, onboot_ifaces_by_policy) task = NetworkInstallationTask(sysroot, self.hostname, disable_ipv6, overwrite, onboot_yes_uuids, network_ifaces) path = self.publish_task(NETWORK.namespace, task) return path def _should_apply_onboot_policy(self): """Should policy for ONBOOT of devices be applied?.""" # Not if any network device was configured via kickstart. if self._original_network_data: return False # Not if any network device was configured in UI. if self._use_device_configurations: return False # Not if there is no configuration to apply the policy to if not self._device_configurations or not self._device_configurations.get_all( ): return False return True def _has_any_onboot_yes_device(self, device_configurations): """Does any device have ONBOOT value set to 'yes'?""" uuids = [ dev_cfg.connection_uuid for dev_cfg in device_configurations.get_all() if dev_cfg.connection_uuid ] for uuid in uuids: ifcfg = get_ifcfg_file([("UUID", uuid)]) if ifcfg: ifcfg.read() if ifcfg.get('ONBOOT') != "no": return True return False def _get_onboot_ifaces_by_policy(self, policy): """Get network interfaces that shoud have ONBOOT set to 'yes' by policy.""" ifaces = [] if policy is NetworkOnBoot.FIRST_WIRED_WITH_LINK: # choose first device having link log.info("Onboot policy: choosing the first device having link.") for device in self.nm_client.get_devices(): if device.get_device_type() not in supported_device_types: continue if device.get_device_type() == NM.DeviceType.WIFI: continue if device.get_carrier(): ifaces.append(device.get_iface()) break elif policy is NetworkOnBoot.DEFAULT_ROUTE_DEVICE: # choose the device used during installation # (ie for majority of cases the one having the default route) log.info("Onboot policy: choosing the default route device.") iface = get_default_route_iface() or get_default_route_iface( family="inet6") if iface: device = self.nm_client.get_device_by_iface(iface) if device.get_device_type() != NM.DeviceType.WIFI: ifaces.append(iface) return ifaces def _update_network_data_with_onboot(self, network_data, ifaces): if not ifaces: return for nd in network_data: supported_devices = self.get_supported_devices() device_name = get_device_name_from_network_data( self.nm_client, nd, supported_devices, self._bootif) if device_name in ifaces: log.debug("Updating network data onboot value: %s -> %s", nd.onboot, True) nd.onboot = True def create_device_configurations(self): """Create and populate the state of network devices configuration.""" self._device_configurations = DeviceConfigurations(self.nm_client) self._device_configurations.configurations_changed.connect( self.device_configurations_changed_cb) self._device_configurations.reload() self._device_configurations.connect() log.debug("Device configurations created: %s", self._device_configurations) def get_device_configurations(self): if not self._device_configurations: return [] return self._device_configurations.get_all() def device_configurations_changed_cb(self, changes): log.debug("Device configurations changed: %s", changes) self.configurations_changed.emit(changes) def consolidate_initramfs_connections(self): """Ensure devices configured in initramfs have no more than one NM connection. In case of multiple connections for device having ifcfg configuration from boot options, the connection should correspond to the ifcfg file. NetworkManager can be generating additional in-memory connection in case it fails to match device configuration to the ifcfg (#1433891). By reactivating the device with ifcfg connection the generated in-memory connection will be deleted by NM. Don't enforce on slave devices for which having multiple connections can be valid (slave connection, regular device connection). """ consolidated_devices = [] for device in self.nm_client.get_devices(): cons = device.get_available_connections() number_of_connections = len(cons) iface = device.get_iface() if number_of_connections < 2: continue # Ignore devices which are slaves if any(con.get_setting_connection().get_master() for con in cons): log.debug( "consolidate %d initramfs connections for %s: it is OK, device is a slave", number_of_connections, iface) continue ifcfg_file = get_ifcfg_file_of_device(self.nm_client, iface) if not ifcfg_file: log.error( "consolidate %d initramfs connections for %s: no ifcfg file", number_of_connections, iface) continue else: # Handle only ifcfgs created from boot options in initramfs # (Kickstart based ifcfgs are handled when applying kickstart) if ifcfg_file.is_from_kickstart: continue log.debug( "consolidate %d initramfs connections for %s: ensure active ifcfg connection", number_of_connections, iface) ensure_active_connection_for_device(self.nm_client, ifcfg_file.uuid, iface, only_replace=True) consolidated_devices.append(iface) return consolidated_devices def get_supported_devices(self): """Get names of existing supported devices on the system.""" return [ device.get_iface() for device in self.nm_client.get_devices() if device.get_device_type() in supported_device_types ] @property def bootif(self): """Get the value of kickstart --bootif option.""" return self._bootif @bootif.setter def bootif(self, specification): """Set the value of kickstart --bootif option. :param specifiacation: mac address specified in kickstart --bootif option :type specification: str """ self._bootif = specification log.debug("bootif device specification is set to %s", specification) @property def ifname_option_values(self): """Get values of ifname boot option.""" return self._ifname_option_values @ifname_option_values.setter def ifname_option_values(self, values): """Set values of ifname boot option. :param values: list of ifname boot option values :type values: list(str) """ self._ifname_option_values = values log.debug("ifname boot option values are set to %s", values) def apply_kickstart(self): """Apply kickstart configuration which has not already been applied. * Activate configurations created in initramfs if --activate is True. * Create configurations for %pre kickstart commands and activate eventually. :returns: list of devices to which kickstart configuration was applied """ applied_devices = [] if not self._original_network_data: log.debug("No kickstart data to apply.") return [] for network_data in self._original_network_data: # Wireless is not supported if network_data.essid: log.info("Wireless devices configuration is not supported.") continue supported_devices = self.get_supported_devices() device_name = get_device_name_from_network_data( self.nm_client, network_data, supported_devices, self._bootif) if not device_name: log.warning("apply kickstart: --device %s not found", network_data.device) continue ifcfg_file = get_ifcfg_file_of_device(self.nm_client, device_name) if ifcfg_file and ifcfg_file.is_from_kickstart: if network_data.activate: if ensure_active_connection_for_device( self.nm_client, ifcfg_file.uuid, device_name): applied_devices.append(device_name) continue # If there is no kickstart ifcfg from initramfs the command was added # in %pre section after switch root, so apply it now applied_devices.append(device_name) if ifcfg_file: # if the device was already configured in initramfs update the settings con_uuid = ifcfg_file.uuid log.debug("pre kickstart - updating settings %s of device %s", con_uuid, device_name) connection = self.nm_client.get_connection_by_uuid(con_uuid) update_connection_from_ksdata(self.nm_client, connection, network_data, device_name=device_name) if network_data.activate: device = self.nm_client.get_device_by_iface(device_name) self.nm_client.activate_connection_async( connection, device, None, None) log.debug( "pre kickstart - activating connection %s with device %s", con_uuid, device_name) else: log.debug("pre kickstart - adding connection for %s", device_name) add_connection_from_ksdata(self.nm_client, network_data, device_name, activate=network_data.activate) return applied_devices def set_real_onboot_values_from_kickstart(self): """Update ifcfg ONBOOT values according to kickstart configuration. So it reflects the --onboot option. This is needed because: 1) For ifcfg files created in initramfs we use ONBOOT for --activate 2) For kickstart applied in stage 2 we can't set the autoconnect setting of connection because the device would be activated immediately. :return: list of devices for which ONBOOT was updated :rtype: list(str) """ updated_devices = [] if not self._original_network_data: log.debug("No kickstart data to set onboot from.") return [] for network_data in self._original_network_data: supported_devices = self.get_supported_devices() device_name = get_device_name_from_network_data( self.nm_client, network_data, supported_devices, self._bootif) if not device_name: log.warning("set ONBOOT: --device %s does not exist", network_data.device) devices_to_update = [device_name] master = device_name # When defining both bond/team and vlan in one command we need more care # network --onboot yes --device bond0 --bootproto static --bondslaves ens9,ens10 # --bondopts mode=active-backup,miimon=100,primary=ens9,fail_over_mac=2 # --ip 192.168.111.1 --netmask 255.255.255.0 --gateway 192.168.111.222 --noipv6 # --vlanid 222 --no-activate if network_data.vlanid and (network_data.bondslaves or network_data.teamslaves): master = network_data.device devices_to_update.append(master) for devname in devices_to_update: if network_data.onboot: # We need to handle "no" -> "yes" change by changing ifcfg file instead of the NM connection # so the device does not get autoactivated (BZ #1261864) uuid = find_ifcfg_uuid_of_device(self.nm_client, devname) or "" if not update_onboot_value( uuid, network_data.onboot, root_path=""): continue else: n_cons = update_iface_setting_values( self.nm_client, devname, [("connection", NM.SETTING_CONNECTION_AUTOCONNECT, network_data.onboot)]) if n_cons != 1: log.debug("set ONBOOT: %d connections found for %s", n_cons, devname) if n_cons > 1: # In case of multiple connections for a device, update ifcfg directly uuid = find_ifcfg_uuid_of_device( self.nm_client, devname) or "" if not update_onboot_value( uuid, network_data.onboot, root_path=""): continue updated_devices.append(devname) # Handle slaves if there are any if network_data.bondslaves or network_data.teamslaves or network_data.bridgeslaves: # Master can be identified by devname or uuid, try to find master uuid uuid = None device = self.nm_client.get_device_by_iface(master) if device: cons = device.get_available_connections() n_cons = len(cons) if n_cons == 1: uuid = cons[0].get_uuid() else: log.debug("set ONBOOT: %d connections found for %s", n_cons, master) updated_slaves = update_slaves_onboot_value( self.nm_client, master, network_data.onboot, uuid=uuid) updated_devices.extend(updated_slaves) return updated_devices def dump_missing_ifcfg_files(self): """Dump missing default ifcfg file for wired devices. Make sure each supported wired device has ifcfg file. For default auto connections created by NM upon start (which happens in case of missing ifcfg file, eg the file was not created in initramfs) rename the in-memory connection using device name and dump it into ifcfg file. If default auto connections are turned off by NM configuration (based on policy, eg on RHEL or server), the connection will be created by Anaconda and dumped into ifcfg file. The connection id (and consequently ifcfg file name) is set to device name. """ if not self.nm_available: return [] new_ifcfgs = [] for device in self.nm_client.get_devices(): if device.get_device_type() not in supported_wired_device_types: continue iface = device.get_iface() if get_ifcfg_file_of_device(self.nm_client, iface): continue cons = device.get_available_connections() n_cons = len(cons) device_is_slave = any(con.get_setting_connection().get_master() for con in cons) if n_cons == 0: data = self.get_kickstart_handler() default_data = data.NetworkData(onboot=False, ipv6="auto") log.debug( "dump missing ifcfgs: creating default connection for %s", iface) add_connection_from_ksdata(self.nm_client, default_data, iface) elif n_cons == 1: if device_is_slave: log.debug( "dump missing ifcfgs: not creating default connection for slave device %s", iface) continue con = cons[0] log.debug( "dump missing ifcfgs: dumping default autoconnection %s for %s", 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) if not bound_hwaddr_of_device(self.nm_client, iface, self.ifname_option_values): s_wired = con.get_setting_wired() s_wired.set_property(NM.SETTING_WIRED_MAC_ADDRESS, None) else: log.debug( "dump missing ifcfgs: iface %s bound to mac address by ifname boot option" ) con.commit_changes(True, None) elif n_cons > 1: if not device_is_slave: log.warning( "dump missing ifcfgs: %d non-slave connections found for device %s", n_cons, iface) continue new_ifcfgs.append(iface) return new_ifcfgs def network_device_configuration_changed(self): if not self._device_configurations: log.error( "Got request to use DeviceConfigurations that has not been created yet" ) self._use_device_configurations = True def get_dracut_arguments(self, iface, target_ip, hostname): """Get dracut arguments for the iface and iSCSI target. The dracut arguments would activate the iface in initramfs so that the iSCSI target can be attached (for example to mount root filesystem). :param iface: network interface used to connect to the target :param target_ip: IP of the iSCSI target :param hostname: static hostname to be configured """ dracut_args = [] if iface not in (device.get_iface() for device in self.nm_client.get_devices()): log.error("get dracut arguments for %s: device not found", iface) return dracut_args ifcfg = get_ifcfg_file_of_device(self.nm_client, iface) if not ifcfg: log.error("get dracut arguments for %s: no ifcfg file found", iface) return dracut_args dracut_args = list( get_dracut_arguments_from_ifcfg(self.nm_client, ifcfg, iface, target_ip, hostname)) return dracut_args
class NetworkModule(KickstartModule): """The Network module.""" def __init__(self): super().__init__() self._firewall_module = FirewallModule() self.hostname_changed = Signal() self._hostname = "localhost.localdomain" self.current_hostname_changed = Signal() self._hostname_service_proxy = None if conf.system.provides_system_bus: self._hostname_service_proxy = HOSTNAME.get_proxy() self._hostname_service_proxy.PropertiesChanged.connect(self._hostname_service_properties_changed) self.connected_changed = Signal() self.nm_client = None # TODO fallback solution - use Gio/GNetworkMonitor ? if SystemBus.check_connection(): nm_client = NM.Client.new(None) if nm_client.get_nm_running(): self.nm_client = nm_client self.nm_client.connect("notify::%s" % NM.CLIENT_STATE, self._nm_state_changed) initial_state = self.nm_client.get_state() self.set_connected(self._nm_state_connected(initial_state)) else: log.debug("NetworkManager is not running.") self._original_network_data = [] self._onboot_yes_ifaces = [] self._device_configurations = None self._use_device_configurations = False self.configurations_changed = Signal() self._default_device_specification = DEFAULT_DEVICE_SPECIFICATION self._bootif = None self._ifname_option_values = [] self._disable_ipv6 = False self._apply_boot_options(flags.cmdline) def publish(self): """Publish the module.""" self._firewall_module.publish() DBus.publish_object(NETWORK.object_path, NetworkInterface(self)) DBus.register_service(NETWORK.service_name) @property def kickstart_specification(self): """Return the kickstart specification.""" return NetworkKickstartSpecification @property def default_device_specification(self): """Get the default specification for missing kickstart --device option.""" return self._default_device_specification @default_device_specification.setter def default_device_specification(self, specification): """Set the default specification for missing kickstart --device option. :param specifiacation: device specification accepted by network --device option :type specification: str """ self._default_device_specification = specification log.debug("default kickstart device specification set to %s", specification) def process_kickstart(self, data): """Process the kickstart data.""" log.debug("Processing kickstart data...") # Handle default value for --device spec = self.default_device_specification if update_network_data_with_default_device(data.network.network, spec): log.debug("used '%s' for missing network --device options", spec) if update_first_network_command_activate_value(data.network.network): log.debug("updated activate value of the first network command (None -> True)") self._original_network_data = data.network.network if data.network.hostname: self.set_hostname(data.network.hostname) self._firewall_module.process_kickstart(data) def generate_kickstart(self): """Return the kickstart string.""" data = self.get_kickstart_handler() if self._device_configurations and self._use_device_configurations: log.debug("using device configurations to generate kickstart") device_data = self.generate_kickstart_network_data(data.NetworkData) else: log.debug("using original kickstart data to generate kickstart") device_data = self._original_network_data data.network.network = device_data self._update_network_data_with_onboot(data.network.network, self._onboot_yes_ifaces) hostname_data = data.NetworkData(hostname=self.hostname, bootProto="") update_network_hostname_data(data.network.network, hostname_data) # firewall self._firewall_module.setup_kickstart(data) return str(data) def _is_device_activated(self, iface): device = self.nm_client.get_device_by_iface(iface) return device and device.get_state() == NM.DeviceState.ACTIVATED def generate_kickstart_network_data(self, network_data_class): rv = [] for cfg in self._device_configurations.get_all(): network_data = None if cfg.device_type != NM.DeviceType.WIFI and cfg.connection_uuid: ifcfg = get_ifcfg_file([("UUID", cfg.connection_uuid)]) if not ifcfg: log.debug("Ifcfg file for %s not found.") continue network_data = get_kickstart_network_data(ifcfg, self.nm_client, network_data_class) if not network_data: log.debug("Device configuration %s does not generate any kickstart data", cfg) continue if cfg.device_name: if self._is_device_activated(cfg.device_name): network_data.activate = True else: # First network command defaults to --activate so we must # use --no-activate explicitly to prevent the default # (Default value is None) if not rv: network_data.activate = False rv.append(network_data) return rv @property def hostname(self): """Return the hostname.""" return self._hostname def set_hostname(self, hostname): """Set the hostname.""" self._hostname = hostname self.hostname_changed.emit() log.debug("Hostname is set to %s", hostname) def _hostname_service_properties_changed(self, interface, changed, invalid): if interface == HOSTNAME.interface_name and "Hostname" in changed: hostname = changed["Hostname"] self.current_hostname_changed.emit(hostname) log.debug("Current hostname changed to %s", hostname) def get_current_hostname(self): """Return current hostname of the system.""" if self._hostname_service_proxy: return self._hostname_service_proxy.Hostname log.debug("Current hostname cannot be get.") return "" def set_current_hostname(self, hostname): """Set current system hostname.""" if not self._hostname_service_proxy: log.debug("Current hostname cannot be set.") return self._hostname_service_proxy.SetHostname(hostname, False) log.debug("Current hostname is set to %s", hostname) @property def nm_available(self): return self.nm_client is not None @property def connected(self): """Is the system connected to the network?""" if self.nm_available: return self._connected else: log.debug("Connectivity state can't be determined, assuming connected.") return True def set_connected(self, connected): """Set network connectivity status.""" self._connected = connected self.connected_changed.emit() self.module_properties_changed.emit() log.debug("Connected to network: %s", connected) def is_connecting(self): """Is NM in connecting state?""" if self.nm_available: return self.nm_client.get_state() == NM.State.CONNECTING else: log.debug("Connectivity state can't be determined, assuming not connecting.") return False @staticmethod def _nm_state_connected(state): return state in (NM.State.CONNECTED_LOCAL, NM.State.CONNECTED_SITE, NM.State.CONNECTED_GLOBAL) def _nm_state_changed(self, *args): state = self.nm_client.get_state() log.debug("NeworkManager state changed to %s", state) self.set_connected(self._nm_state_connected(state)) @property def disable_ipv6(self): """Disable IPv6 on target system.""" return self._disable_ipv6 @disable_ipv6.setter def disable_ipv6(self, disable_ipv6): """Set disable IPv6 on target system. :param disable_ipv6: should ipv6 be disabled on target system :type disable_ipv6: bool """ self._disable_ipv6 = disable_ipv6 log.debug("disable IPv6 is set to %s", disable_ipv6) def install_network_with_task(self, sysroot, onboot_ifaces, overwrite): """Install network with an installation task. :param sysroot: a path to the root of the installed system :param onboot_ifaces: list of network interfaces which should have ONBOOT=yes :param overwrite: overwrite existing configuration :return: a DBus path of an installation task """ disable_ipv6 = self.disable_ipv6 and devices_ignore_ipv6(self.nm_client, supported_wired_device_types) network_ifaces = [device.get_iface() for device in self.nm_client.get_devices()] onboot_ifaces_by_policy = [] if self._should_apply_onboot_policy() and \ not self._has_any_onboot_yes_device(self._device_configurations): onboot_ifaces_by_policy = self._get_onboot_ifaces_by_policy(conf.network.default_on_boot) all_onboot_ifaces = list(set(onboot_ifaces + onboot_ifaces_by_policy)) self._onboot_yes_ifaces = all_onboot_ifaces onboot_yes_uuids = [find_ifcfg_uuid_of_device(self.nm_client, iface) or "" for iface in all_onboot_ifaces] log.debug("ONBOOT will be set to yes for %s (fcoe) %s (policy)", onboot_ifaces, onboot_ifaces_by_policy) task = NetworkInstallationTask(sysroot, self.hostname, disable_ipv6, overwrite, onboot_yes_uuids, network_ifaces, self.ifname_option_values) task.succeeded_signal.connect(lambda: self.log_task_result(task, root_path=sysroot)) path = self.publish_task(NETWORK.namespace, task) return path def _should_apply_onboot_policy(self): """Should policy for ONBOOT of devices be applied?.""" # Not if any network device was configured via kickstart. if self._original_network_data: return False # Not if any network device was configured in UI. if self._use_device_configurations: return False # Not if there is no configuration to apply the policy to if not self._device_configurations or not self._device_configurations.get_all(): return False return True def _has_any_onboot_yes_device(self, device_configurations): """Does any device have ONBOOT value set to 'yes'?""" uuids = [dev_cfg.connection_uuid for dev_cfg in device_configurations.get_all() if dev_cfg.connection_uuid] for uuid in uuids: ifcfg = get_ifcfg_file([("UUID", uuid)]) if ifcfg: ifcfg.read() if ifcfg.get('ONBOOT') != "no": return True return False def _get_onboot_ifaces_by_policy(self, policy): """Get network interfaces that shoud have ONBOOT set to 'yes' by policy.""" ifaces = [] if policy is NetworkOnBoot.FIRST_WIRED_WITH_LINK: # choose first device having link log.info("Onboot policy: choosing the first device having link.") for device in self.nm_client.get_devices(): if device.get_device_type() not in supported_device_types: continue if device.get_device_type() == NM.DeviceType.WIFI: continue if device.get_carrier(): ifaces.append(device.get_iface()) break elif policy is NetworkOnBoot.DEFAULT_ROUTE_DEVICE: # choose the device used during installation # (ie for majority of cases the one having the default route) log.info("Onboot policy: choosing the default route device.") iface = get_default_route_iface() or get_default_route_iface(family="inet6") if iface: device = self.nm_client.get_device_by_iface(iface) if device.get_device_type() != NM.DeviceType.WIFI: ifaces.append(iface) return ifaces def _update_network_data_with_onboot(self, network_data, ifaces): if not ifaces: return for nd in network_data: supported_devices = [dev_info.device_name for dev_info in self.get_supported_devices()] device_name = get_device_name_from_network_data(self.nm_client, nd, supported_devices, self.bootif) if device_name in ifaces: log.debug("Updating network data onboot value: %s -> %s", nd.onboot, True) nd.onboot = True def create_device_configurations(self): """Create and populate the state of network devices configuration.""" if not self.nm_available: log.debug("Device configurations can't be created, no NetworkManager available.") return self._device_configurations = DeviceConfigurations(self.nm_client) self._device_configurations.configurations_changed.connect(self.device_configurations_changed_cb) self._device_configurations.reload() self._device_configurations.connect() log.debug("Device configurations created: %s", self._device_configurations) def get_device_configurations(self): if not self._device_configurations: return [] return self._device_configurations.get_all() def device_configurations_changed_cb(self, changes): log.debug("Device configurations changed: %s", changes) self.configurations_changed.emit(changes) def consolidate_initramfs_connections_with_task(self): """Ensure devices configured in initramfs have no more than one NM connection. In case of multiple connections for device having ifcfg configuration from boot options, the connection should correspond to the ifcfg file. NetworkManager can be generating additional in-memory connection in case it fails to match device configuration to the ifcfg (#1433891). By reactivating the device with ifcfg connection the generated in-memory connection will be deleted by NM. Don't enforce on slave devices for which having multiple connections can be valid (slave connection, regular device connection). :returns: DBus path of the task consolidating the connections """ task = ConsolidateInitramfsConnectionsTask(self.nm_client) task.succeeded_signal.connect(lambda: self.log_task_result(task, check_result=True)) return self.publish_task(NETWORK.namespace, task, NetworkInitializationTaskInterface) def get_supported_devices(self): """Get information about existing supported devices on the system. :return: list of objects describing found supported devices :rtype: list(NetworkDeviceInfo) """ # TODO guard on system (provides_system_bus) supported_devices = [] if not self.nm_available: log.debug("Supported devices can't be determined.") return supported_devices for device in self.nm_client.get_devices(): if device.get_device_type() not in supported_device_types: continue dev_info = NetworkDeviceInfo() dev_info.set_from_nm_device(device) if not all((dev_info.device_name, dev_info.device_type, dev_info.hw_address)): log.warning("Missing value when setting NetworkDeviceInfo from NM device: %s", dev_info) supported_devices.append(dev_info) return supported_devices def get_activated_interfaces(self): """Get activated network interfaces. Device is considered as activated if it has an active network (NM) connection. :return: list of names of devices having active network connection :rtype: list(str) """ # TODO guard on system (provides_system_bus) activated_ifaces = [] if not self.nm_available: log.debug("Activated interfaces can't be determined.") return activated_ifaces for ac in self.nm_client.get_active_connections(): if ac.get_state() != NM.ActiveConnectionState.ACTIVATED: continue for device in ac.get_devices(): activated_ifaces.append(device.get_ip_iface() or device.get_iface()) return activated_ifaces @property def bootif(self): """Get the value of kickstart --device bootif option.""" return self._bootif @bootif.setter def bootif(self, specification): """Set the value of kickstart --device bootif option. :param specifiacation: mac address specified by kickstart --device bootif option :type specification: str """ self._bootif = specification log.debug("bootif device specification is set to %s", specification) @property def ifname_option_values(self): """Get values of ifname boot option.""" return self._ifname_option_values @ifname_option_values.setter def ifname_option_values(self, values): """Set values of ifname boot option. :param values: list of ifname boot option values :type values: list(str) """ self._ifname_option_values = values log.debug("ifname boot option values are set to %s", values) def apply_kickstart_with_task(self): """Apply kickstart configuration which has not already been applied. * Activate configurations created in initramfs if --activate is True. * Create configurations for %pre kickstart commands and activate eventually. :returns: DBus path of the task applying the kickstart """ supported_devices = [dev_info.device_name for dev_info in self.get_supported_devices()] task = ApplyKickstartTask(self.nm_client, self._original_network_data, supported_devices, self.bootif, self.ifname_option_values) task.succeeded_signal.connect(lambda: self.log_task_result(task, check_result=True)) return self.publish_task(NETWORK.namespace, task, NetworkInitializationTaskInterface) def set_real_onboot_values_from_kickstart_with_task(self): """Update ifcfg ONBOOT values according to kickstart configuration. So it reflects the --onboot option. This is needed because: 1) For ifcfg files created in initramfs we use ONBOOT for --activate 2) For kickstart applied in stage 2 we can't set the autoconnect setting of connection because the device would be activated immediately. :returns: DBus path of the task setting the values """ supported_devices = [dev_info.device_name for dev_info in self.get_supported_devices()] task = SetRealOnbootValuesFromKickstartTask(self.nm_client, self._original_network_data, supported_devices, self.bootif, self.ifname_option_values) task.succeeded_signal.connect(lambda: self.log_task_result(task, check_result=True)) return self.publish_task(NETWORK.namespace, task, NetworkInitializationTaskInterface) def dump_missing_ifcfg_files_with_task(self): """Dump missing default ifcfg file for wired devices. Make sure each supported wired device has ifcfg file. For default auto connections created by NM upon start (which happens in case of missing ifcfg file, eg the file was not created in initramfs) rename the in-memory connection using device name and dump it into ifcfg file. If default auto connections are turned off by NM configuration (based on policy, eg on RHEL or server), the connection will be created by Anaconda and dumped into ifcfg file. The connection id (and consequently ifcfg file name) is set to device name. :returns: DBus path of the task dumping the files """ data = self.get_kickstart_handler() default_network_data = data.NetworkData(onboot=False, ipv6="auto") task = DumpMissingIfcfgFilesTask(self.nm_client, default_network_data, self.ifname_option_values) task.succeeded_signal.connect(lambda: self.log_task_result(task, check_result=True)) return self.publish_task(NETWORK.namespace, task, NetworkInitializationTaskInterface) def network_device_configuration_changed(self): if not self._device_configurations: log.error("Got request to use DeviceConfigurations that has not been created yet") self._use_device_configurations = True def get_dracut_arguments(self, iface, target_ip, hostname): """Get dracut arguments for the iface and iSCSI target. The dracut arguments would activate the iface in initramfs so that the iSCSI target can be attached (for example to mount root filesystem). :param iface: network interface used to connect to the target :param target_ip: IP of the iSCSI target :param hostname: static hostname to be configured """ dracut_args = [] if not self.nm_available: log.debug("Dracut arguments can't be obtained, no NetworkManager available.") return dracut_args if iface not in (device.get_iface() for device in self.nm_client.get_devices()): log.error("get dracut arguments for %s: device not found", iface) return dracut_args ifcfg = get_ifcfg_file_of_device(self.nm_client, iface) if not ifcfg: log.error("get dracut arguments for %s: no ifcfg file found", iface) return dracut_args dracut_args = list(get_dracut_arguments_from_ifcfg(self.nm_client, ifcfg, iface, target_ip, hostname)) return dracut_args def _apply_boot_options(self, kernel_arguments): """Apply boot options to the module. :param kernel_arguments: structure holding installer boot options :type kernel_arguments: KernelArguments """ log.debug("Applying boot options %s", kernel_arguments) if 'ksdevice' in kernel_arguments: self.default_device_specification = kernel_arguments.get('ksdevice') if 'BOOTIF' in kernel_arguments: self.bootif = kernel_arguments['BOOTIF'][3:].replace("-", ":").upper() if 'ifname' in kernel_arguments: self.ifname_option_values = kernel_arguments.get("ifname", "").split() if 'noipv6' in kernel_arguments: self.disable_ipv6 = True def log_task_result(self, task, check_result=False, root_path=""): if not check_result: self.log_configuration_state(task.name, root_path) else: result = task.get_result() log.debug("%s result: %s", task.name, result) if result: self.log_configuration_state(task.name, root_path) def log_configuration_state(self, msg_header, root_path=""): """Log the current network configuration state. Logs ifcfg files and NM connections """ log.debug("Dumping configuration state - %s", msg_header) for line in get_ifcfg_files_content(root_path=root_path).splitlines(): log.debug(line) if self.nm_available: for line in get_connections_dump(self.nm_client).splitlines(): log.debug(line) def set_connection_onboot_value(self, uuid, onboot): """Sets ONBOOT value of connection given by uuid. The value is stored in ifcfg file because setting the value in NetworkManager connection ('autoconnect') to True could cause activating of the connection. :param uuid: UUID of the connection to be set :param onboot: value of ONBOOT for the connection """ return update_onboot_value(uuid, onboot) def get_connection_onboot_value(self, uuid): """Gets ONBOOT value of connection given by uuid. The value is stored in ifcfg file because setting the value in NetworkManager connection ('autoconnect') to True could cause activating of the connection. :param uuid: UUID of the connection :return: ONBOOT value """ ifcfg = get_ifcfg_file([('UUID', uuid)]) if not ifcfg: log.error("Can't get ONBOOT value for connection %s", uuid) return False ifcfg.read() return ifcfg.get('ONBOOT') != "no"