635 lines
26 KiB
Python
635 lines
26 KiB
Python
|
# Network configuration spoke classes
|
||
|
#
|
||
|
# Copyright (C) 2013 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
|
||
|
|
||
|
import socket
|
||
|
|
||
|
from pyanaconda import network
|
||
|
from pyanaconda.core.configuration.anaconda import conf
|
||
|
from pyanaconda.modules.common.constants.services import NETWORK
|
||
|
from pyanaconda.modules.common.structures.network import NetworkDeviceConfiguration
|
||
|
from pyanaconda.flags import flags
|
||
|
from pyanaconda.ui.categories.system import SystemCategory
|
||
|
from pyanaconda.ui.tui.spokes import NormalTUISpoke
|
||
|
from pyanaconda.ui.tui.tuiobject import Dialog, report_if_failed
|
||
|
from pyanaconda.ui.common import FirstbootSpokeMixIn
|
||
|
from pyanaconda.core.i18n import N_, _
|
||
|
from pyanaconda.core.regexes import IPV4_PATTERN_WITH_ANCHORS, IPV4_NETMASK_WITH_ANCHORS, IPV4_OR_DHCP_PATTERN_WITH_ANCHORS
|
||
|
from pyanaconda.core.constants import ANACONDA_ENVIRON
|
||
|
from pyanaconda.anaconda_loggers import get_module_logger
|
||
|
|
||
|
from simpleline.render.containers import ListColumnContainer
|
||
|
from simpleline.render.prompt import Prompt
|
||
|
from simpleline.render.screen import InputState
|
||
|
from simpleline.render.screen_handler import ScreenHandler
|
||
|
from simpleline.render.widgets import TextWidget, CheckboxWidget, EntryWidget
|
||
|
|
||
|
log = get_module_logger(__name__)
|
||
|
|
||
|
# This will be used in decorators in ConfigureDeviceSpoke.
|
||
|
# The decorators are processed before the class is created so you can have this as a variable there.
|
||
|
IP_ERROR_MSG = N_("Bad format of the IP address")
|
||
|
NETMASK_ERROR_MSG = N_("Bad format of the netmask")
|
||
|
|
||
|
__all__ = ["NetworkSpoke"]
|
||
|
|
||
|
|
||
|
# TODO: use our own datastore?
|
||
|
class WiredTUIConfigurationData():
|
||
|
"""Holds tui input configuration data of wired device."""
|
||
|
def __init__(self):
|
||
|
self.ip = "dhcp"
|
||
|
self.netmask = ""
|
||
|
self.gateway = ""
|
||
|
self.ipv6 = "auto"
|
||
|
self.ipv6gateway = ""
|
||
|
self.nameserver = ""
|
||
|
self.ipv6addrgenmode = NM.SettingIP6ConfigAddrGenMode.EUI64
|
||
|
self.onboot = False
|
||
|
|
||
|
def set_from_connection(self, connection):
|
||
|
"""Set the object from NM RemoteConnection.
|
||
|
|
||
|
:param connection: connection to be used to set the object
|
||
|
:type connection: NM.RemoteConnection
|
||
|
"""
|
||
|
connection_uuid = connection.get_uuid()
|
||
|
|
||
|
ip4_config = connection.get_setting_ip4_config()
|
||
|
ip4_method = ip4_config.get_method()
|
||
|
if ip4_method == NM.SETTING_IP4_CONFIG_METHOD_AUTO:
|
||
|
self.ip = "dhcp"
|
||
|
elif ip4_method == NM.SETTING_IP4_CONFIG_METHOD_MANUAL:
|
||
|
if ip4_config.get_num_addresses() > 0:
|
||
|
addr = ip4_config.get_address(0)
|
||
|
self.ip = addr.get_address()
|
||
|
self.netmask = network.prefix_to_netmask(addr.get_prefix())
|
||
|
else:
|
||
|
log.error("No ip4 address found for manual method in %s", connection_uuid)
|
||
|
elif ip4_method == NM.SETTING_IP4_CONFIG_METHOD_DISABLED:
|
||
|
self.ip = ""
|
||
|
else:
|
||
|
log.error("Unexpected ipv4 method %s found in connection %s", ip4_method, connection_uuid)
|
||
|
self.ip = "dhcp"
|
||
|
self.gateway = ip4_config.get_gateway() or ""
|
||
|
|
||
|
ip6_config = connection.get_setting_ip6_config()
|
||
|
self.ipv6addrgenmode = ip6_config.get_addr_gen_mode()
|
||
|
ip6_method = ip6_config.get_method()
|
||
|
if ip6_method == NM.SETTING_IP6_CONFIG_METHOD_AUTO:
|
||
|
self.ipv6 = "auto"
|
||
|
elif ip6_method == NM.SETTING_IP6_CONFIG_METHOD_IGNORE:
|
||
|
self.ipv6 = "ignore"
|
||
|
elif ip6_method == NM.SETTING_IP6_CONFIG_METHOD_DHCP:
|
||
|
self.ipv6 = "dhcp"
|
||
|
elif ip6_method == NM.SETTING_IP6_CONFIG_METHOD_MANUAL:
|
||
|
if ip6_config.get_num_addresses() > 0:
|
||
|
addr = ip6_config.get_address(0)
|
||
|
self.ipv6 = "{}/{}".format(addr.get_address(), addr.get_prefix())
|
||
|
else:
|
||
|
log.error("No ip6 address found for manual method in %s", connection_uuid)
|
||
|
else:
|
||
|
log.error("Unexpected ipv6 method %s found in connection %s", ip6_method, connection_uuid)
|
||
|
self.ipv6 = "auto"
|
||
|
self.ipv6gateway = ip6_config.get_gateway() or ""
|
||
|
|
||
|
nameservers = []
|
||
|
for i in range(0, ip4_config.get_num_dns()):
|
||
|
nameservers.append(ip4_config.get_dns(i))
|
||
|
for i in range(0, ip6_config.get_num_dns()):
|
||
|
nameservers.append(ip6_config.get_dns(i))
|
||
|
self.nameserver = ",".join(nameservers)
|
||
|
|
||
|
self.onboot = connection.get_setting_connection().get_autoconnect()
|
||
|
|
||
|
def update_connection(self, connection):
|
||
|
"""Update NM RemoteConnection from the object.
|
||
|
|
||
|
:param connection: connection to be updated from the object
|
||
|
:type connection: NM.RemoteConnection
|
||
|
"""
|
||
|
# ipv4 settings
|
||
|
if self.ip == "dhcp":
|
||
|
method4 = NM.SETTING_IP4_CONFIG_METHOD_AUTO
|
||
|
elif self.ip:
|
||
|
method4 = NM.SETTING_IP4_CONFIG_METHOD_MANUAL
|
||
|
else:
|
||
|
method4 = NM.SETTING_IP4_CONFIG_METHOD_DISABLED
|
||
|
|
||
|
connection.remove_setting(NM.SettingIP4Config)
|
||
|
s_ip4 = NM.SettingIP4Config.new()
|
||
|
s_ip4.set_property(NM.SETTING_IP_CONFIG_METHOD, method4)
|
||
|
if method4 == NM.SETTING_IP4_CONFIG_METHOD_MANUAL:
|
||
|
prefix4 = network.netmask_to_prefix(self.netmask)
|
||
|
addr4 = NM.IPAddress.new(socket.AF_INET, self.ip, prefix4)
|
||
|
s_ip4.add_address(addr4)
|
||
|
if self.gateway:
|
||
|
s_ip4.props.gateway = self.gateway
|
||
|
connection.add_setting(s_ip4)
|
||
|
|
||
|
# ipv6 settings
|
||
|
if self.ipv6 == "ignore":
|
||
|
method6 = NM.SETTING_IP6_CONFIG_METHOD_IGNORE
|
||
|
elif not self.ipv6 or self.ipv6 == "auto":
|
||
|
method6 = NM.SETTING_IP6_CONFIG_METHOD_AUTO
|
||
|
elif self.ipv6 == "dhcp":
|
||
|
method6 = NM.SETTING_IP6_CONFIG_METHOD_DHCP
|
||
|
else:
|
||
|
method6 = NM.SETTING_IP6_CONFIG_METHOD_MANUAL
|
||
|
|
||
|
connection.remove_setting(NM.SettingIP6Config)
|
||
|
s_ip6 = NM.SettingIP6Config.new()
|
||
|
s_ip6.set_property(NM.SETTING_IP6_CONFIG_ADDR_GEN_MODE, self.ipv6addrgenmode)
|
||
|
s_ip6.set_property(NM.SETTING_IP_CONFIG_METHOD, method6)
|
||
|
if method6 == NM.SETTING_IP6_CONFIG_METHOD_MANUAL:
|
||
|
addr6, _slash, prefix6 = self.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 self.ipv6gateway:
|
||
|
s_ip6.props.gateway = self.ipv6gateway
|
||
|
connection.add_setting(s_ip6)
|
||
|
|
||
|
# nameservers
|
||
|
if self.nameserver:
|
||
|
for ns in [str.strip(i) for i in self.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)
|
||
|
|
||
|
s_con = connection.get_setting_connection()
|
||
|
s_con.set_property(NM.SETTING_CONNECTION_AUTOCONNECT, self.onboot)
|
||
|
|
||
|
def __str__(self):
|
||
|
return "WiredTUIConfigurationData ip:{} netmask:{} gateway:{} ipv6:{} ipv6gateway:{} " \
|
||
|
"nameserver:{} onboot:{} addr-gen-mode:{}".format(self.ip, self.netmask, self.gateway,
|
||
|
self.ipv6, self.ipv6gateway,
|
||
|
self.nameserver, self.onboot,
|
||
|
self.ipv6addrgenmode)
|
||
|
|
||
|
|
||
|
class NetworkSpoke(FirstbootSpokeMixIn, NormalTUISpoke):
|
||
|
""" Spoke used to configure network settings.
|
||
|
|
||
|
.. inheritance-diagram:: NetworkSpoke
|
||
|
:parts: 3
|
||
|
"""
|
||
|
category = SystemCategory
|
||
|
configurable_device_types = [
|
||
|
NM.DeviceType.ETHERNET,
|
||
|
NM.DeviceType.INFINIBAND,
|
||
|
]
|
||
|
|
||
|
@staticmethod
|
||
|
def get_screen_id():
|
||
|
"""Return a unique id of this UI screen."""
|
||
|
return "network-configuration"
|
||
|
|
||
|
def __init__(self, data, storage, payload):
|
||
|
NormalTUISpoke.__init__(self, data, storage, payload)
|
||
|
self.title = N_("Network configuration")
|
||
|
self._network_module = NETWORK.get_proxy()
|
||
|
|
||
|
self.nm_client = network.get_nm_client()
|
||
|
if not self.nm_client and conf.system.provides_system_bus:
|
||
|
self.nm_client = NM.Client.new(None)
|
||
|
|
||
|
self._container = None
|
||
|
self.hostname = self._network_module.Hostname
|
||
|
self.editable_configurations = []
|
||
|
self.errors = []
|
||
|
self._apply = False
|
||
|
|
||
|
@classmethod
|
||
|
def should_run(cls, environment, data):
|
||
|
"""Should the spoke run?"""
|
||
|
if not FirstbootSpokeMixIn.should_run(environment, data):
|
||
|
return False
|
||
|
|
||
|
return conf.system.can_configure_network
|
||
|
|
||
|
def initialize(self):
|
||
|
self.initialize_start()
|
||
|
NormalTUISpoke.initialize(self)
|
||
|
self._update_editable_configurations()
|
||
|
self._network_module.DeviceConfigurationChanged.connect(self._device_configurations_changed)
|
||
|
self.initialize_done()
|
||
|
|
||
|
def _device_configurations_changed(self, device_configurations):
|
||
|
log.debug("device configurations changed: %s", device_configurations)
|
||
|
self._update_editable_configurations()
|
||
|
|
||
|
def _update_editable_configurations(self):
|
||
|
device_configurations = NetworkDeviceConfiguration.from_structure_list(
|
||
|
self._network_module.GetDeviceConfigurations()
|
||
|
)
|
||
|
self.editable_configurations = [dc for dc in device_configurations
|
||
|
if dc.device_type in self.configurable_device_types]
|
||
|
|
||
|
@property
|
||
|
def completed(self):
|
||
|
""" Check whether this spoke is complete or not."""
|
||
|
# If we can't configure network, don't require it
|
||
|
return (not conf.system.can_configure_network
|
||
|
or self._network_module.IsConnecting
|
||
|
or self._network_module.Connected)
|
||
|
|
||
|
@property
|
||
|
def mandatory(self):
|
||
|
# the network spoke should be mandatory only if it is running
|
||
|
# during the installation and if the installation source requires network
|
||
|
return ANACONDA_ENVIRON in flags.environs and self.payload.needs_network
|
||
|
|
||
|
@property
|
||
|
def status(self):
|
||
|
""" Short msg telling what devices are active. """
|
||
|
return network.status_message(self.nm_client)
|
||
|
|
||
|
def _summary_text(self):
|
||
|
"""Devices cofiguration shown to user."""
|
||
|
msg = ""
|
||
|
activated_devs = self._network_module.GetActivatedInterfaces()
|
||
|
for device_configuration in self.editable_configurations:
|
||
|
name = device_configuration.device_name
|
||
|
if name in activated_devs:
|
||
|
msg += self._activated_device_msg(name)
|
||
|
else:
|
||
|
msg += _("Wired (%(interface_name)s) disconnected\n") \
|
||
|
% {"interface_name": name}
|
||
|
return msg
|
||
|
|
||
|
def _activated_device_msg(self, devname):
|
||
|
msg = _("Wired (%(interface_name)s) connected\n") \
|
||
|
% {"interface_name": devname}
|
||
|
|
||
|
device = self.nm_client.get_device_by_iface(devname)
|
||
|
if device:
|
||
|
addr_str = dnss_str = gateway_str = netmask_str = ""
|
||
|
ipv4config = device.get_ip4_config()
|
||
|
if ipv4config:
|
||
|
addresses = ipv4config.get_addresses()
|
||
|
if addresses:
|
||
|
a0 = addresses[0]
|
||
|
addr_str = a0.get_address()
|
||
|
prefix = a0.get_prefix()
|
||
|
netmask_str = network.prefix_to_netmask(prefix)
|
||
|
gateway_str = ipv4config.get_gateway() or ''
|
||
|
dnss_str = ",".join(ipv4config.get_nameservers())
|
||
|
msg += _(" IPv4 Address: %(addr)s Netmask: %(netmask)s Gateway: %(gateway)s\n") % \
|
||
|
{"addr": addr_str, "netmask": netmask_str, "gateway": gateway_str}
|
||
|
msg += _(" DNS: %s\n") % dnss_str
|
||
|
|
||
|
ipv6config = device.get_ip6_config()
|
||
|
if ipv6config:
|
||
|
for address in ipv6config.get_addresses():
|
||
|
addr_str = address.get_address()
|
||
|
prefix = address.get_prefix()
|
||
|
# Do not display link-local addresses
|
||
|
if not addr_str.startswith("fe80:"):
|
||
|
msg += _(" IPv6 Address: %(addr)s/%(prefix)d\n") % \
|
||
|
{"addr": addr_str, "prefix": prefix}
|
||
|
return msg
|
||
|
|
||
|
def refresh(self, args=None):
|
||
|
""" Refresh screen. """
|
||
|
NormalTUISpoke.refresh(self, args)
|
||
|
|
||
|
self._container = ListColumnContainer(1, columns_width=78, spacing=1)
|
||
|
|
||
|
if not self.nm_client:
|
||
|
self.window.add_with_separator(TextWidget(_("Network configuration is not available.")))
|
||
|
return
|
||
|
|
||
|
summary = self._summary_text()
|
||
|
self.window.add_with_separator(TextWidget(summary))
|
||
|
|
||
|
hostname = _("Host Name: %s\n") % self._network_module.Hostname
|
||
|
self.window.add_with_separator(TextWidget(hostname))
|
||
|
current_hostname = _("Current host name: %s\n") % self._network_module.GetCurrentHostname()
|
||
|
self.window.add_with_separator(TextWidget(current_hostname))
|
||
|
|
||
|
# if we have any errors, display them
|
||
|
while len(self.errors) > 0:
|
||
|
self.window.add_with_separator(TextWidget(self.errors.pop()))
|
||
|
|
||
|
dialog = Dialog(_("Host Name"))
|
||
|
self._container.add(TextWidget(_("Set host name")), callback=self._set_hostname_callback, data=dialog)
|
||
|
|
||
|
for device_configuration in self.editable_configurations:
|
||
|
iface = device_configuration.device_name
|
||
|
text = (_("Configure device %s") % iface)
|
||
|
self._container.add(TextWidget(text), callback=self._ensure_connection_and_configure,
|
||
|
data=iface)
|
||
|
|
||
|
self.window.add_with_separator(self._container)
|
||
|
|
||
|
def _set_hostname_callback(self, dialog):
|
||
|
self.hostname = dialog.run()
|
||
|
self.redraw()
|
||
|
self.apply()
|
||
|
|
||
|
def _ensure_connection_and_configure(self, iface):
|
||
|
for device_configuration in self.editable_configurations:
|
||
|
if device_configuration.device_name == iface:
|
||
|
connection_uuid = device_configuration.connection_uuid
|
||
|
if connection_uuid:
|
||
|
self._configure_connection(iface, connection_uuid)
|
||
|
else:
|
||
|
device_type = self.nm_client.get_device_by_iface(iface).get_device_type()
|
||
|
connection = get_default_connection(iface, device_type)
|
||
|
connection_uuid = connection.get_uuid()
|
||
|
log.debug("adding default connection %s for %s", connection_uuid, iface)
|
||
|
data = (iface, connection_uuid)
|
||
|
self.nm_client.add_connection2(
|
||
|
connection.to_dbus(NM.ConnectionSerializationFlags.ALL),
|
||
|
(NM.SettingsAddConnection2Flags.TO_DISK |
|
||
|
NM.SettingsAddConnection2Flags.BLOCK_AUTOCONNECT),
|
||
|
None,
|
||
|
False,
|
||
|
None,
|
||
|
self._default_connection_added_cb,
|
||
|
data
|
||
|
)
|
||
|
return
|
||
|
log.error("device configuration for %s not found", iface)
|
||
|
|
||
|
def _default_connection_added_cb(self, client, result, data):
|
||
|
iface, connection_uuid = data
|
||
|
try:
|
||
|
_connection, result = client.add_connection2_finish(result)
|
||
|
except Exception as e: # pylint: disable=broad-except
|
||
|
msg = "adding default connection {} from {} failed: {}".format(
|
||
|
connection_uuid, iface, str(e))
|
||
|
log.error(msg)
|
||
|
self.errors.append(msg)
|
||
|
self.redraw()
|
||
|
else:
|
||
|
log.debug("added default connection %s for %s: %s", connection_uuid, iface, result)
|
||
|
self._configure_connection(iface, connection_uuid)
|
||
|
|
||
|
def _configure_connection(self, iface, connection_uuid):
|
||
|
connection = self.nm_client.get_connection_by_uuid(connection_uuid)
|
||
|
|
||
|
new_spoke = ConfigureDeviceSpoke(self.data, self.storage, self.payload,
|
||
|
self._network_module, iface, connection)
|
||
|
ScreenHandler.push_screen_modal(new_spoke)
|
||
|
|
||
|
if new_spoke.errors:
|
||
|
self.errors.extend(new_spoke.errors)
|
||
|
self.redraw()
|
||
|
return
|
||
|
|
||
|
if new_spoke.apply_configuration:
|
||
|
self._apply = True
|
||
|
|
||
|
self._network_module.LogConfigurationState(
|
||
|
"Settings of {} updated in TUI.".format(iface)
|
||
|
)
|
||
|
|
||
|
self.redraw()
|
||
|
self.apply()
|
||
|
|
||
|
def input(self, args, key):
|
||
|
""" Handle the input. """
|
||
|
if self._container.process_user_input(key):
|
||
|
return InputState.PROCESSED
|
||
|
else:
|
||
|
return super().input(args, key)
|
||
|
|
||
|
def apply(self):
|
||
|
"""Apply all of our settings."""
|
||
|
# Inform network module that device configurations might have been changed
|
||
|
# and we want to generate kickstart from device configurations
|
||
|
# (persistent NM / config files configuration), instead of using original kickstart.
|
||
|
self._network_module.NetworkDeviceConfigurationChanged()
|
||
|
|
||
|
(valid, error) = network.is_valid_hostname(self.hostname, local=True)
|
||
|
if not self.hostname or valid:
|
||
|
self._network_module.Hostname = self.hostname
|
||
|
else:
|
||
|
self.errors.append(_("Host name is not valid: %s") % error)
|
||
|
self.hostname = self._network_module.Hostname
|
||
|
|
||
|
if self._apply:
|
||
|
self._apply = False
|
||
|
if ANACONDA_ENVIRON in flags.environs:
|
||
|
from pyanaconda.payload.manager import payloadMgr
|
||
|
payloadMgr.start(self.payload)
|
||
|
|
||
|
|
||
|
class ConfigureDeviceSpoke(NormalTUISpoke):
|
||
|
""" Spoke to set various configuration options for net devices. """
|
||
|
category = "network"
|
||
|
|
||
|
def __init__(self, data, storage, payload, network_module, iface, connection):
|
||
|
super().__init__(data, storage, payload)
|
||
|
self.title = N_("Device configuration")
|
||
|
|
||
|
self._network_module = network_module
|
||
|
self._container = None
|
||
|
self._connection = connection
|
||
|
self._iface = iface
|
||
|
self._connection_uuid = connection.get_uuid()
|
||
|
self.errors = []
|
||
|
self.apply_configuration = False
|
||
|
|
||
|
self._data = WiredTUIConfigurationData()
|
||
|
self._data.set_from_connection(self._connection)
|
||
|
|
||
|
log.debug("Configure iface %s: connection %s -> %s", self._iface, self._connection_uuid,
|
||
|
self._data)
|
||
|
|
||
|
def refresh(self, args=None):
|
||
|
""" Refresh window. """
|
||
|
super().refresh(args)
|
||
|
|
||
|
self._container = ListColumnContainer(1)
|
||
|
|
||
|
dialog = Dialog(title=(_('IPv4 address or %s for DHCP') % '"dhcp"'),
|
||
|
conditions=[self._check_ipv4_or_dhcp])
|
||
|
self._container.add(EntryWidget(dialog.title, self._data.ip), self._set_ipv4_or_dhcp, dialog)
|
||
|
|
||
|
dialog = Dialog(title=_("IPv4 netmask"), conditions=[self._check_netmask])
|
||
|
self._container.add(EntryWidget(dialog.title, self._data.netmask), self._set_netmask, dialog)
|
||
|
|
||
|
dialog = Dialog(title=_("IPv4 gateway"), conditions=[self._check_ipv4])
|
||
|
self._container.add(EntryWidget(dialog.title, self._data.gateway), self._set_ipv4_gateway, dialog)
|
||
|
|
||
|
msg = (_('IPv6 address[/prefix] or %(auto)s for automatic, %(dhcp)s for DHCP, '
|
||
|
'%(ignore)s to turn off')
|
||
|
% {"auto": '"auto"', "dhcp": '"dhcp"', "ignore": '"ignore"'})
|
||
|
dialog = Dialog(title=msg, conditions=[self._check_ipv6_config])
|
||
|
self._container.add(EntryWidget(dialog.title, self._data.ipv6), self._set_ipv6, dialog)
|
||
|
|
||
|
dialog = Dialog(title=_("IPv6 default gateway"), conditions=[self._check_ipv6])
|
||
|
self._container.add(EntryWidget(dialog.title, self._data.ipv6gateway), self._set_ipv6_gateway, dialog)
|
||
|
|
||
|
dialog = Dialog(title=_("Nameservers (comma separated)"), conditions=[self._check_nameservers])
|
||
|
self._container.add(EntryWidget(dialog.title, self._data.nameserver), self._set_nameservers, dialog)
|
||
|
|
||
|
msg = _("Connect automatically after reboot")
|
||
|
w = CheckboxWidget(title=msg, completed=self._data.onboot)
|
||
|
self._container.add(w, self._set_onboot_handler)
|
||
|
|
||
|
msg = _("Apply configuration in installer")
|
||
|
w = CheckboxWidget(title=msg, completed=self.apply_configuration)
|
||
|
self._container.add(w, self._set_apply_handler)
|
||
|
|
||
|
self.window.add_with_separator(self._container)
|
||
|
|
||
|
message = _("Configuring device %s.") % self._iface
|
||
|
self.window.add_with_separator(TextWidget(message))
|
||
|
|
||
|
@report_if_failed(message=IP_ERROR_MSG)
|
||
|
def _check_ipv4_or_dhcp(self, user_input, report_func):
|
||
|
return IPV4_OR_DHCP_PATTERN_WITH_ANCHORS.match(user_input) is not None
|
||
|
|
||
|
@report_if_failed(message=IP_ERROR_MSG)
|
||
|
def _check_ipv4(self, user_input, report_func):
|
||
|
return IPV4_PATTERN_WITH_ANCHORS.match(user_input) is not None
|
||
|
|
||
|
@report_if_failed(message=NETMASK_ERROR_MSG)
|
||
|
def _check_netmask(self, user_input, report_func):
|
||
|
return IPV4_NETMASK_WITH_ANCHORS.match(user_input) is not None
|
||
|
|
||
|
@report_if_failed(message=IP_ERROR_MSG)
|
||
|
def _check_ipv6(self, user_input, report_func):
|
||
|
return network.check_ip_address(user_input, version=6)
|
||
|
|
||
|
@report_if_failed(message=IP_ERROR_MSG)
|
||
|
def _check_ipv6_config(self, user_input, report_func):
|
||
|
if user_input in ["auto", "dhcp", "ignore"]:
|
||
|
return True
|
||
|
addr, _slash, prefix = user_input.partition("/")
|
||
|
if prefix:
|
||
|
try:
|
||
|
if not 1 <= int(prefix) <= 128:
|
||
|
return False
|
||
|
except ValueError:
|
||
|
return False
|
||
|
return network.check_ip_address(addr, version=6)
|
||
|
|
||
|
@report_if_failed(message=IP_ERROR_MSG)
|
||
|
def _check_nameservers(self, user_input, report_func):
|
||
|
if user_input.strip():
|
||
|
addresses = [str.strip(i) for i in user_input.split(",")]
|
||
|
for ip in addresses:
|
||
|
if not network.check_ip_address(ip):
|
||
|
return False
|
||
|
return True
|
||
|
|
||
|
def _set_ipv4_or_dhcp(self, dialog):
|
||
|
self._data.ip = dialog.run()
|
||
|
|
||
|
def _set_netmask(self, dialog):
|
||
|
self._data.netmask = dialog.run()
|
||
|
|
||
|
def _set_ipv4_gateway(self, dialog):
|
||
|
self._data.gateway = dialog.run()
|
||
|
|
||
|
def _set_ipv6(self, dialog):
|
||
|
self._data.ipv6 = dialog.run()
|
||
|
|
||
|
def _set_ipv6_gateway(self, dialog):
|
||
|
self._data.ipv6gateway = dialog.run()
|
||
|
|
||
|
def _set_nameservers(self, dialog):
|
||
|
self._data.nameserver = dialog.run()
|
||
|
|
||
|
def _set_apply_handler(self, args):
|
||
|
self.apply_configuration = not self.apply_configuration
|
||
|
|
||
|
def _set_onboot_handler(self, args):
|
||
|
self._data.onboot = not self._data.onboot
|
||
|
|
||
|
def input(self, args, key):
|
||
|
if self._container.process_user_input(key):
|
||
|
return InputState.PROCESSED_AND_REDRAW
|
||
|
else:
|
||
|
if key.lower() == Prompt.CONTINUE:
|
||
|
if self._data.ip != "dhcp" and not self._data.netmask:
|
||
|
self.errors.append(_("Configuration not saved: netmask missing in static configuration"))
|
||
|
else:
|
||
|
self.apply()
|
||
|
return InputState.PROCESSED_AND_CLOSE
|
||
|
else:
|
||
|
return super().input(args, key)
|
||
|
|
||
|
@property
|
||
|
def indirect(self):
|
||
|
return True
|
||
|
|
||
|
def apply(self):
|
||
|
"""Apply changes to NM connection."""
|
||
|
log.debug("updating connection %s:\n%s", self._connection_uuid,
|
||
|
self._connection.to_dbus(NM.ConnectionSerializationFlags.ALL))
|
||
|
|
||
|
updated_connection = NM.SimpleConnection.new_clone(self._connection)
|
||
|
self._data.update_connection(updated_connection)
|
||
|
|
||
|
# Commit the changes
|
||
|
self._connection.update2(
|
||
|
updated_connection.to_dbus(NM.ConnectionSerializationFlags.ALL),
|
||
|
NM.SettingsUpdate2Flags.TO_DISK | NM.SettingsUpdate2Flags.BLOCK_AUTOCONNECT,
|
||
|
None,
|
||
|
None,
|
||
|
self._connection_updated_cb,
|
||
|
self._connection_uuid
|
||
|
)
|
||
|
|
||
|
def _connection_updated_cb(self, connection, result, connection_uuid):
|
||
|
connection.update2_finish(result)
|
||
|
log.debug("updated connection %s:\n%s", connection_uuid,
|
||
|
connection.to_dbus(NM.ConnectionSerializationFlags.ALL))
|
||
|
if self.apply_configuration:
|
||
|
nm_client = network.get_nm_client()
|
||
|
device = nm_client.get_device_by_iface(self._iface)
|
||
|
log.debug("activating connection %s with device %s",
|
||
|
connection_uuid, self._iface)
|
||
|
nm_client.activate_connection_async(connection, device, None, None)
|
||
|
|
||
|
|
||
|
def get_default_connection(iface, device_type):
|
||
|
"""Get default connection to be edited by the UI."""
|
||
|
connection = NM.SimpleConnection.new()
|
||
|
s_con = NM.SettingConnection.new()
|
||
|
s_con.props.uuid = NM.utils_uuid_generate()
|
||
|
s_con.props.autoconnect = True
|
||
|
s_con.props.id = iface
|
||
|
s_con.props.interface_name = iface
|
||
|
if device_type == NM.DeviceType.ETHERNET:
|
||
|
s_con.props.type = "802-3-ethernet"
|
||
|
s_wired = NM.SettingWired.new()
|
||
|
connection.add_setting(s_wired)
|
||
|
elif device_type == NM.DeviceType.INFINIBAND:
|
||
|
s_con.props.type = "infiniband"
|
||
|
s_ib = NM.SettingInfiniband.new()
|
||
|
s_ib.props.transport_mode = "datagram"
|
||
|
connection.add_setting(s_ib)
|
||
|
connection.add_setting(s_con)
|
||
|
return connection
|