608 lines
22 KiB
Python
608 lines
22 KiB
Python
#
|
|
# network.py - network configuration install data
|
|
#
|
|
# Copyright (C) 2001, 2002, 2003, 2004, 2005, 2006, 2007 Red Hat, Inc.
|
|
# 2008, 2009, 2017
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation; either version 2 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty 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, see <http://www.gnu.org/licenses/>.
|
|
import shutil
|
|
import socket
|
|
import itertools
|
|
import os
|
|
import time
|
|
import threading
|
|
import re
|
|
import ipaddress
|
|
|
|
from dasbus.typing import get_native
|
|
|
|
from pyanaconda.anaconda_loggers import get_module_logger
|
|
from pyanaconda.core import util, constants
|
|
from pyanaconda.core.i18n import _
|
|
from pyanaconda.core.kernel import kernel_arguments
|
|
from pyanaconda.core.path import make_directories
|
|
from pyanaconda.core.regexes import HOSTNAME_PATTERN_WITHOUT_ANCHORS, \
|
|
IPV6_ADDRESS_IN_DRACUT_IP_OPTION, MAC_OCTET
|
|
from pyanaconda.core.configuration.anaconda import conf
|
|
from pyanaconda.core.constants import TIME_SOURCE_SERVER
|
|
from pyanaconda.modules.common.constants.services import NETWORK, TIMEZONE, STORAGE
|
|
from pyanaconda.modules.common.constants.objects import FCOE
|
|
from pyanaconda.modules.common.task import sync_run_task
|
|
from pyanaconda.modules.common.structures.network import NetworkDeviceInfo
|
|
from pyanaconda.modules.common.structures.timezone import TimeSourceData
|
|
from pyanaconda.modules.common.util import is_module_available
|
|
|
|
import gi
|
|
gi.require_version("NM", "1.0")
|
|
from gi.repository import NM
|
|
|
|
log = get_module_logger(__name__)
|
|
|
|
network_connected = None
|
|
network_connected_condition = threading.Condition()
|
|
|
|
_nm_client = None
|
|
|
|
__all__ = ["get_supported_devices", "status_message", "wait_for_connectivity",
|
|
"wait_for_connecting_NM_thread", "wait_for_network_devices", "wait_for_connected_NM",
|
|
"initialize_network", "copy_resolv_conf_to_root", "prefix_to_netmask",
|
|
"netmask_to_prefix", "get_first_ip_address", "is_valid_hostname", "check_ip_address",
|
|
"get_nm_client", "write_configuration"]
|
|
|
|
|
|
def get_nm_client():
|
|
"""Get NetworkManager Client."""
|
|
if conf.system.provides_system_bus:
|
|
global _nm_client
|
|
if not _nm_client:
|
|
_nm_client = NM.Client.new(None)
|
|
return _nm_client
|
|
else:
|
|
log.debug("NetworkManager client not available (system does not provide it).")
|
|
return None
|
|
|
|
|
|
def check_ip_address(address, version=None):
|
|
"""Check if the given IP address is valid in given version if set.
|
|
|
|
:param str address: IP address for testing
|
|
:param int version: ``4`` for IPv4, ``6`` for IPv6 or
|
|
``None`` to allow either format
|
|
:returns: ``True`` if IP address is valid or ``False`` if not
|
|
:rtype: bool
|
|
"""
|
|
try:
|
|
if version == 4:
|
|
ipaddress.IPv4Address(address)
|
|
elif version == 6:
|
|
ipaddress.IPv6Address(address)
|
|
elif not version: # any of those
|
|
ipaddress.ip_address(address)
|
|
else:
|
|
log.error("IP version %s is not supported", version)
|
|
return False
|
|
return True
|
|
except ValueError:
|
|
return False
|
|
|
|
|
|
def is_valid_hostname(hostname, local=False):
|
|
"""Check if the given string is (syntactically) a valid hostname.
|
|
|
|
:param str hostname: a string to check
|
|
:param bool local: is the hostname static (for this system) or not (on the network)
|
|
:returns: a pair containing boolean value (valid or invalid) and
|
|
an error message (if applicable)
|
|
:rtype: (bool, str)
|
|
"""
|
|
if not hostname:
|
|
return (False, _("Host name cannot be None or an empty string."))
|
|
|
|
if len(hostname) > 64:
|
|
return (False, _("Host name must be 64 or fewer characters in length."))
|
|
|
|
if local and hostname[-1] == ".":
|
|
return (False, _("Local host name must not end with period '.'."))
|
|
|
|
if not re.match('^' + HOSTNAME_PATTERN_WITHOUT_ANCHORS + '$', hostname):
|
|
return (False, _("Host names can only contain the characters 'a-z', "
|
|
"'A-Z', '0-9', '-', or '.', parts between periods "
|
|
"must contain something and cannot start or end with "
|
|
"'-'."))
|
|
|
|
return (True, "")
|
|
|
|
|
|
def get_ip_addresses():
|
|
"""Return a list of IP addresses for all active devices."""
|
|
ipv4_addresses = []
|
|
ipv6_addresses = []
|
|
for device in get_activated_devices(get_nm_client()):
|
|
ipv4_addresses += get_device_ip_addresses(device, version=4)
|
|
ipv6_addresses += get_device_ip_addresses(device, version=6)
|
|
# prefer IPv4 addresses to IPv6 addresses
|
|
return ipv4_addresses + ipv6_addresses
|
|
|
|
|
|
def get_first_ip_address():
|
|
"""Return the first non-local IP of active devices.
|
|
|
|
:return: IP address assigned to an active device
|
|
:rtype: str or None
|
|
"""
|
|
for ip in get_ip_addresses():
|
|
if ip not in ("127.0.0.1", "::1"):
|
|
return ip
|
|
return None
|
|
|
|
|
|
def netmask_to_prefix(netmask):
|
|
""" Convert netmask to prefix (CIDR bits) """
|
|
prefix = 0
|
|
|
|
while prefix < 33:
|
|
if prefix_to_netmask(prefix) == netmask:
|
|
return prefix
|
|
|
|
prefix += 1
|
|
|
|
return prefix
|
|
|
|
|
|
def prefix_to_netmask(prefix):
|
|
""" Convert prefix (CIDR bits) to netmask """
|
|
_bytes = []
|
|
for _i in range(4):
|
|
if prefix >= 8:
|
|
_bytes.append(255)
|
|
prefix -= 8
|
|
else:
|
|
_bytes.append(256 - 2 ** (8 - prefix))
|
|
prefix = 0
|
|
netmask = ".".join(str(byte) for byte in _bytes)
|
|
return netmask
|
|
|
|
|
|
def hostname_from_cmdline(kernel_args):
|
|
"""Get hostname defined by boot options.
|
|
|
|
:param kernel_args: structure holding installer boot options
|
|
:type kernel_args: KernelArguments
|
|
"""
|
|
# legacy hostname= option
|
|
hostname = kernel_args.get('hostname', "")
|
|
# ip= option (man dracut.cmdline)
|
|
ipopts = kernel_args.get('ip')
|
|
# Example (2 options):
|
|
# ens3:dhcp 10.34.102.244::10.34.102.54:255.255.255.0:myhostname:ens9:none
|
|
if ipopts:
|
|
for ipopt in ipopts.split(" "):
|
|
if ipopt.startswith("["):
|
|
# Replace ipv6 addresses with empty string, example of ipv6 config:
|
|
# [fd00:10:100::84:5]::[fd00:10:100::86:49]:80:myhostname:ens9:none
|
|
ipopt = IPV6_ADDRESS_IN_DRACUT_IP_OPTION.sub('', ipopt)
|
|
elements = ipopt.split(':')
|
|
# Hostname can be defined only in option having more than 5 elements.
|
|
# But filter out auto ip= with mac address set by MAC_OCTET matching, eg:
|
|
# ip=<interface>:dhcp::52:54:00:12:34:56
|
|
# where the 4th element is not hostname.
|
|
if len(elements) > 5 and not re.match(MAC_OCTET, elements[5]):
|
|
hostname = ipopt.split(':')[4]
|
|
return hostname
|
|
|
|
|
|
def iface_for_host_ip(host_ip):
|
|
"""Get interface used to access given host IP."""
|
|
route = util.execWithCapture("ip", ["route", "get", "to", host_ip])
|
|
if not route:
|
|
log.error("Could not get interface for route to %s", host_ip)
|
|
return ""
|
|
|
|
route_info = route.split()
|
|
if route_info[0] != host_ip or len(route_info) < 5 or \
|
|
"dev" not in route_info or route_info.index("dev") > 3:
|
|
log.error('Unexpected "ip route get to %s" reply: %s', host_ip, route_info)
|
|
return ""
|
|
|
|
return route_info[route_info.index("dev") + 1]
|
|
|
|
|
|
def copy_resolv_conf_to_root(root="/"):
|
|
"""Copy resolv.conf to a system root."""
|
|
src = "/etc/resolv.conf"
|
|
dst = os.path.join(root, src.lstrip('/'))
|
|
if not os.path.isfile(src):
|
|
log.debug("%s does not exist", src)
|
|
return
|
|
if os.path.isfile(dst):
|
|
log.debug("%s already exists", dst)
|
|
return
|
|
dst_dir = os.path.dirname(dst)
|
|
if not os.path.isdir(dst_dir):
|
|
make_directories(dst_dir)
|
|
shutil.copyfile(src, dst)
|
|
|
|
|
|
def run_network_initialization_task(task_path):
|
|
"""Run network initialization task and log the result."""
|
|
task_proxy = NETWORK.get_proxy(task_path)
|
|
log.debug("Running task %s", task_proxy.Name)
|
|
sync_run_task(task_proxy)
|
|
result = get_native(task_proxy.GetResult())
|
|
msg = "%s result: %s" % (task_proxy.Name, result)
|
|
log.debug(msg)
|
|
|
|
|
|
def initialize_network():
|
|
"""Initialize networking."""
|
|
if not conf.system.can_configure_network:
|
|
return
|
|
|
|
network_proxy = NETWORK.get_proxy()
|
|
|
|
msg = "Initialization started."
|
|
log.debug(msg)
|
|
network_proxy.LogConfigurationState(msg)
|
|
|
|
log.debug("Devices found: %s",
|
|
[dev.device_name for dev in get_supported_devices()])
|
|
|
|
run_network_initialization_task(network_proxy.ApplyKickstartWithTask())
|
|
run_network_initialization_task(network_proxy.DumpMissingConfigFilesWithTask())
|
|
|
|
if not network_proxy.Hostname:
|
|
bootopts_hostname = hostname_from_cmdline(kernel_arguments)
|
|
if bootopts_hostname:
|
|
log.debug("Updating host name from boot options: %s", bootopts_hostname)
|
|
network_proxy.Hostname = bootopts_hostname
|
|
|
|
# Create device configuration tracking in the module.
|
|
# It will be used to generate kickstart from persistent network configuration
|
|
# managed by NM (having config files) and updated by NM signals on device
|
|
# configuration changes.
|
|
log.debug("Creating network configurations.")
|
|
network_proxy.CreateDeviceConfigurations()
|
|
|
|
log.debug("Initialization finished.")
|
|
|
|
|
|
def write_configuration(overwrite=False):
|
|
"""Install network configuration to target system."""
|
|
fcoe_proxy = STORAGE.get_proxy(FCOE)
|
|
fcoe_nics = fcoe_proxy.GetNics()
|
|
fcoe_ifaces = [dev.device_name for dev in get_supported_devices()
|
|
if dev.device_name in fcoe_nics]
|
|
network_proxy = NETWORK.get_proxy()
|
|
|
|
task_path = network_proxy.ConfigureActivationOnBootWithTask(fcoe_ifaces)
|
|
task_proxy = NETWORK.get_proxy(task_path)
|
|
sync_run_task(task_proxy)
|
|
|
|
task_path = network_proxy.InstallNetworkWithTask(overwrite)
|
|
task_proxy = NETWORK.get_proxy(task_path)
|
|
sync_run_task(task_proxy)
|
|
|
|
task_path = network_proxy.ConfigureHostnameWithTask(overwrite)
|
|
task_proxy = NETWORK.get_proxy(task_path)
|
|
sync_run_task(task_proxy)
|
|
|
|
if conf.system.can_change_hostname:
|
|
hostname = network_proxy.Hostname
|
|
if hostname:
|
|
network_proxy.SetCurrentHostname(hostname)
|
|
|
|
|
|
def _set_ntp_servers_from_dhcp():
|
|
"""Set NTP servers of timezone module from dhcp if not set by kickstart."""
|
|
# FIXME - do it only if they will be applied (the guard at the end of the function)
|
|
if not is_module_available(TIMEZONE):
|
|
return
|
|
|
|
timezone_proxy = TIMEZONE.get_proxy()
|
|
ntp_servers = get_ntp_servers_from_dhcp(get_nm_client())
|
|
log.info("got %d NTP servers from DHCP", len(ntp_servers))
|
|
hostnames = []
|
|
for server_address in ntp_servers:
|
|
try:
|
|
hostname = socket.gethostbyaddr(server_address)[0]
|
|
except socket.error:
|
|
# getting hostname failed, just use the address returned from DHCP
|
|
log.debug("getting NTP server host name failed for address: %s",
|
|
server_address)
|
|
hostname = server_address
|
|
hostnames.append(hostname)
|
|
|
|
# check if some NTP servers were specified from kickstart
|
|
if not timezone_proxy.TimeSources and conf.target.is_hardware:
|
|
# no NTP servers were specified, add those from DHCP
|
|
servers = []
|
|
|
|
for hostname in hostnames:
|
|
server = TimeSourceData()
|
|
server.type = TIME_SOURCE_SERVER
|
|
server.hostname = hostname
|
|
server.options = ["iburst"]
|
|
servers.append(server)
|
|
|
|
timezone_proxy.TimeSources = \
|
|
TimeSourceData.to_structure_list(servers)
|
|
|
|
|
|
def wait_for_connected_NM(timeout=constants.NETWORK_CONNECTION_TIMEOUT, only_connecting=False):
|
|
"""Wait for NM being connected.
|
|
|
|
If only_connecting is set, wait only if NM is in connecting state and
|
|
return immediately after leaving this state (regardless of the new state).
|
|
Used to wait for dhcp configuration in progress.
|
|
|
|
:param timeout: timeout in seconds
|
|
:type timeout: int
|
|
:parm only_connecting: wait only for the result of NM being connecting
|
|
:type only_connecting: bool
|
|
:return: NM is connected
|
|
:rtype: bool
|
|
"""
|
|
|
|
network_proxy = NETWORK.get_proxy()
|
|
if network_proxy.Connected:
|
|
return True
|
|
|
|
if only_connecting:
|
|
if network_proxy.IsConnecting():
|
|
log.debug("waiting for connecting NM (dhcp in progress?), timeout=%d", timeout)
|
|
else:
|
|
return False
|
|
else:
|
|
log.debug("waiting for connected NM, timeout=%d", timeout)
|
|
|
|
i = 0
|
|
while i < timeout:
|
|
i += constants.NETWORK_CONNECTED_CHECK_INTERVAL
|
|
time.sleep(constants.NETWORK_CONNECTED_CHECK_INTERVAL)
|
|
if network_proxy.Connected:
|
|
log.debug("NM connected, waited %d seconds", i)
|
|
return True
|
|
elif only_connecting:
|
|
if not network_proxy.IsConnecting():
|
|
break
|
|
|
|
log.debug("NM not connected, waited %d seconds", i)
|
|
return False
|
|
|
|
|
|
def wait_for_network_devices(devices, timeout=constants.NETWORK_CONNECTION_TIMEOUT):
|
|
"""Wait for network devices to be activated with a connection."""
|
|
devices = set(devices)
|
|
i = 0
|
|
log.debug("waiting for connection of devices %s for iscsi", devices)
|
|
while i < timeout:
|
|
network_proxy = NETWORK.get_proxy()
|
|
activated_devices = network_proxy.GetActivatedInterfaces()
|
|
if not devices - set(activated_devices):
|
|
return True
|
|
i += 1
|
|
time.sleep(1)
|
|
return False
|
|
|
|
|
|
def wait_for_connecting_NM_thread():
|
|
"""Wait for connecting NM in thread, do some work and signal connectivity.
|
|
|
|
This function is called from a thread which is run at startup to wait for
|
|
NetworkManager being in connecting state (eg getting IP from DHCP). When NM
|
|
leaves connecting state do some actions and signal new state if NM becomes
|
|
connected.
|
|
"""
|
|
connected = wait_for_connected_NM(only_connecting=True)
|
|
if connected:
|
|
_set_ntp_servers_from_dhcp()
|
|
with network_connected_condition:
|
|
global network_connected
|
|
network_connected = connected
|
|
network_connected_condition.notify_all()
|
|
|
|
|
|
def wait_for_connectivity(timeout=constants.NETWORK_CONNECTION_TIMEOUT):
|
|
"""Wait for network connectivty to become available
|
|
|
|
:param timeout: how long to wait in seconds
|
|
:type timeout: integer of float"""
|
|
connected = False
|
|
network_connected_condition.acquire()
|
|
# if network_connected is None, network connectivity check
|
|
# has not yet been run or is in progress, so wait for it to finish
|
|
if network_connected is None:
|
|
# wait releases the lock and reacquires it once the thread is unblocked
|
|
network_connected_condition.wait(timeout=timeout)
|
|
connected = network_connected
|
|
# after wait() unblocks, we get the lock back,
|
|
# so we need to release it
|
|
network_connected_condition.release()
|
|
return connected
|
|
|
|
|
|
def get_activated_devices(nm_client):
|
|
"""Get activated NetworkManager devices."""
|
|
activated_devices = []
|
|
|
|
if not nm_client:
|
|
return activated_devices
|
|
|
|
for ac in nm_client.get_active_connections():
|
|
if ac.get_state() != NM.ActiveConnectionState.ACTIVATED:
|
|
continue
|
|
for device in ac.get_devices():
|
|
activated_devices.append(device)
|
|
return activated_devices
|
|
|
|
|
|
def status_message(nm_client):
|
|
"""A short string describing which devices are connected."""
|
|
|
|
msg = _("Unknown")
|
|
|
|
if not nm_client:
|
|
msg = _("Status not available")
|
|
return msg
|
|
|
|
state = nm_client.get_state()
|
|
if state == NM.State.CONNECTING:
|
|
msg = _("Connecting...")
|
|
elif state == NM.State.DISCONNECTING:
|
|
msg = _("Disconnecting...")
|
|
else:
|
|
active_devs = [d for d in get_activated_devices(nm_client)
|
|
if not is_libvirt_device(d.get_ip_iface() or d.get_iface())]
|
|
if active_devs:
|
|
|
|
ports = {}
|
|
ssids = {}
|
|
nonports = []
|
|
|
|
# first find ports and wireless aps
|
|
for device in active_devs:
|
|
device_ports = []
|
|
if hasattr(device, 'get_slaves'):
|
|
device_ports = [port_dev.get_iface() for port_dev in device.get_slaves()]
|
|
iface = device.get_iface()
|
|
ports[iface] = device_ports
|
|
if device.get_device_type() == NM.DeviceType.WIFI:
|
|
ssid = ""
|
|
ap = device.get_active_access_point()
|
|
if ap:
|
|
ssid = ap.get_ssid().get_data().decode()
|
|
ssids[iface] = ssid
|
|
all_ports = set(itertools.chain.from_iterable(ports.values()))
|
|
nonports = [dev for dev in active_devs if dev.get_iface() not in all_ports]
|
|
|
|
if len(nonports) == 1:
|
|
device = nonports[0]
|
|
iface = device.get_ip_iface() or device.get_iface()
|
|
device_type = device.get_device_type()
|
|
if device_type_is_supported_wired(device_type):
|
|
msg = _("Wired (%(interface_name)s) connected") \
|
|
% {"interface_name": iface}
|
|
elif device_type == NM.DeviceType.WIFI:
|
|
msg = _("Wireless connected to %(access_point)s") \
|
|
% {"access_point": ssids[iface]}
|
|
elif device_type == NM.DeviceType.BOND:
|
|
msg = _("Bond %(interface_name)s (%(list_of_ports)s) connected") \
|
|
% {"interface_name": iface,
|
|
"list_of_ports": ",".join(ports[iface])}
|
|
elif device_type == NM.DeviceType.TEAM:
|
|
msg = _("Team %(interface_name)s (%(list_of_ports)s) connected") \
|
|
% {"interface_name": iface,
|
|
"list_of_ports": ",".join(ports[iface])}
|
|
elif device_type == NM.DeviceType.BRIDGE:
|
|
msg = _("Bridge %(interface_name)s (%(list_of_ports)s) connected") \
|
|
% {"interface_name": iface,
|
|
"list_of_ports": ",".join(ports[iface])}
|
|
elif device_type == NM.DeviceType.VLAN:
|
|
parent = device.get_parent()
|
|
vlanid = device.get_vlan_id()
|
|
msg = _("VLAN %(interface_name)s (%(parent_device)s, ID %(vlanid)s) connected") \
|
|
% {"interface_name": iface, "parent_device": parent, "vlanid": vlanid}
|
|
elif len(nonports) > 1:
|
|
devlist = []
|
|
for device in nonports:
|
|
iface = device.get_ip_iface() or device.get_iface()
|
|
device_type = device.get_device_type()
|
|
if device_type_is_supported_wired(device_type):
|
|
devlist.append("%s" % iface)
|
|
elif device_type == NM.DeviceType.WIFI:
|
|
devlist.append("%s" % ssids[iface])
|
|
elif device_type == NM.DeviceType.BOND:
|
|
devlist.append("%s (%s)" % (iface, ",".join(ports[iface])))
|
|
elif device_type == NM.DeviceType.TEAM:
|
|
devlist.append("%s (%s)" % (iface, ",".join(ports[iface])))
|
|
elif device_type == NM.DeviceType.BRIDGE:
|
|
devlist.append("%s (%s)" % (iface, ",".join(ports[iface])))
|
|
elif device_type == NM.DeviceType.VLAN:
|
|
devlist.append("%s" % iface)
|
|
msg = _("Connected: %(list_of_interface_names)s") % {"list_of_interface_names": ", ".join(devlist)}
|
|
else:
|
|
msg = _("Not connected")
|
|
|
|
if not get_supported_devices():
|
|
msg = _("No network devices available")
|
|
|
|
return msg
|
|
|
|
|
|
def get_supported_devices():
|
|
"""Get existing network devices supported by the installer.
|
|
|
|
:return: basic information about the devices
|
|
:rtype: list(NetworkDeviceInfo)
|
|
"""
|
|
network_proxy = NETWORK.get_proxy()
|
|
return NetworkDeviceInfo.from_structure_list(network_proxy.GetSupportedDevices())
|
|
|
|
|
|
def get_ntp_servers_from_dhcp(nm_client):
|
|
"""Return IPs of NTP servers obtained by DHCP.
|
|
|
|
:param nm_client: instance of NetworkManager client
|
|
:type nm_client: NM.Client
|
|
:return: IPs of NTP servers obtained by DHCP
|
|
:rtype: list of str
|
|
"""
|
|
ntp_servers = []
|
|
|
|
if not nm_client:
|
|
return ntp_servers
|
|
|
|
for device in get_activated_devices(nm_client):
|
|
dhcp4_config = device.get_dhcp4_config()
|
|
if dhcp4_config:
|
|
options = dhcp4_config.get_options()
|
|
ntp_servers_string = options.get("ntp_servers")
|
|
if ntp_servers_string:
|
|
ntp_servers.extend(ntp_servers_string.split(" "))
|
|
# NetworkManager does not request NTP/SNTP options for DHCP6
|
|
|
|
return ntp_servers
|
|
|
|
|
|
def get_device_ip_addresses(device, version=4):
|
|
"""Get IP addresses of the device.
|
|
|
|
Ignores ipv6 link-local addresses.
|
|
|
|
:param device: NetworkManager device object
|
|
:type device: NMDevice
|
|
:param version: IP version (4 or 6)
|
|
:type version: int
|
|
"""
|
|
addresses = []
|
|
|
|
if version == 4:
|
|
ipv4_config = device.get_ip4_config()
|
|
if ipv4_config:
|
|
addresses = [addr.get_address() for addr in ipv4_config.get_addresses()]
|
|
elif version == 6:
|
|
ipv6_config = device.get_ip6_config()
|
|
if ipv6_config:
|
|
all_addresses = [addr.get_address() for addr in ipv6_config.get_addresses()]
|
|
addresses = [addr for addr in all_addresses
|
|
if not addr.startswith("fe80:")]
|
|
return addresses
|
|
|
|
|
|
def is_libvirt_device(iface):
|
|
return iface and iface.startswith("virbr")
|
|
|
|
|
|
def device_type_is_supported_wired(device_type):
|
|
return device_type in [NM.DeviceType.ETHERNET, NM.DeviceType.INFINIBAND]
|