src/include/network/routines.rb
# ***************************************************************************
#
# Copyright (c) 2012 Novell, Inc.
# All Rights Reserved.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of version 2 of the GNU General Public License as
# published by the Free Software Foundation.
#
# 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, contact Novell, Inc.
#
# To contact Novell about this file by physical or electronic mail,
# you may find current contact information at www.novell.com
#
# **************************************************************************
# File: include/network/routines.ycp
# Package: Network configuration
# Summary: Miscellaneous routines
# Authors: Michal Svec <msvec@suse.cz>
#
require "shellwords"
module Yast
module NetworkRoutinesInclude
include I18n
include Yast
include Logger
def initialize_network_routines(_include_target)
Yast.import "UI"
textdomain "network"
Yast.import "Popup"
Yast.import "Progress"
Yast.import "String"
Yast.import "Arch"
Yast.import "Confirm"
Yast.import "Map"
Yast.import "Netmask"
Yast.import "Mode"
Yast.import "IP"
Yast.import "TypeRepository"
Yast.import "Stage"
Yast.import "PackagesProposal"
Yast.import "Report"
end
# Abort function
# @return blah blah lahjk
def Abort
return false if Mode.commandline
UI.PollInput == :abort
end
# Check for pending Abort press
# @return true if pending abort
def PollAbort
UI.PollInput == :abort
end
# If modified, ask for confirmation
# @return true if abort is confirmed
def ReallyAbort
Popup.ReallyAbort(true)
end
# If modified, ask for confirmation
# @param [Boolean] modified true if modified
# @return true if abort is confirmed
def ReallyAbortCond(modified)
!modified || Popup.ReallyAbort(true)
end
# Progress::NextStage and Progress::Title combined into one function
# @param [String] title progressbar title
def ProgressNextStage(title)
Progress.NextStage
Progress.Title(title)
nil
end
# Adds the packages to the software proposal to make sure they are available
# in the installed system
# @param [Array<String>] packages list of required packages (["rpm", "bash"])
# @return :next in any case
def add_pkgs_to_proposal(packages)
log.info "Adding network packages to proposal: #{packages}"
PackagesProposal.AddResolvables("network", :package, packages) unless packages.empty?
:next
end
# Check if required packages are installed and install them if they're not
# @param [Array<String>] packages list of required packages (["rpm", "bash"])
# @return `next if packages installation is successfull, `abort otherwise
def PackagesInstall(packages)
packages = deep_copy(packages)
return :next if packages == []
log.info "Checking packages: #{packages}"
# bnc#888130 In inst-sys, there is no RPM database to check
# If the required package is part of the inst-sys, it will work,
# if not, package can't be installed anyway
#
# Ideas:
# - check /.packages.* for presence of the required package
# - use `extend` to load the required packages on-the-fly
return :next if Stage.initial
Yast.import "Package"
return :next if Package.InstalledAll(packages)
# Popup text
text = _("These packages need to be installed:") + "<p>"
Builtins.foreach(packages) do |l|
text = Ops.add(text, Builtins.sformat("%1<br>", l))
end
Builtins.y2debug("Installing packages: %1", text)
ret = false
loop do
ret = Package.InstallAll(packages)
break if ret == true
if ret == false && Package.InstalledAll(packages)
ret = true
break
end
# Popup text
if !Popup.YesNo(
_(
"The required packages are not installed.\n" \
"The configuration will be aborted.\n" \
"\n" \
"Try again?\n"
) + "\n"
)
break
end
end
(ret == true) ? :next : :abort
end
# Checks if given value is emtpy.
def IsEmpty(value)
value = deep_copy(value)
TypeRepository.IsEmpty(value)
end
def DistinguishedName(name, hwdevice)
hwdevice = deep_copy(hwdevice)
if Ops.get_string(hwdevice, "sysfs_bus_id", "") != ""
return Builtins.sformat(
"%1 (%2)",
name,
Ops.get_string(hwdevice, "sysfs_bus_id", "")
)
end
name
end
# Extract the device 'name'
# @param [Hash] hwdevice hardware device
# @return name consisting of vendor and device name
def DeviceName(hwdevice)
device = hwdevice["device"] || ""
return device if !device.empty?
model = hwdevice["model"] || ""
return model if !model.empty?
vendor = hwdevice["sub_vendor"] || ""
dev = hwdevice["sub_device"] || ""
if vendor.empty? || dev.empty?
vendor = hwdevice["vendor"] || ""
dev = hwdevice["device"] || ""
end
"#{vendor} #{dev}".strip
end
# Simple convertor from subclass to controller type.
# @param [Hash] hwdevice map with card info containing "subclass"
# @return short device name
# @example ControllerType(<ethernet controller map>) -> "eth"
def ControllerType(hwdevice)
hwdevice = deep_copy(hwdevice)
return "modem" if Ops.get_string(hwdevice, "subclass", "") == "Modem"
return "isdn" if Ops.get_string(hwdevice, "subclass", "") == "ISDN"
return "dsl" if Ops.get_string(hwdevice, "subclass", "") == "DSL"
subclass_id = Ops.get_integer(hwdevice, "sub_class_id", -1)
# Network controller
if Ops.get_integer(hwdevice, "class_id", -1) == 2
case subclass_id
when 0
return "eth"
when 1
return "tr"
when 2
return "fddi"
when 3
return "atm"
when 4
return "isdn"
when 6, 7 ## Should be PICMG?
return "ib"
when 129
return "myri"
when 130
return "wlan"
when 131
return "xp"
when 134
return "qeth"
when 135
return "hsi"
when 136
return "ctc"
when 137
return "lcs"
when 142
return "ficon"
when 143
return "escon"
when 144
return "iucv"
when 145
return "usb" # #22739
when 128
# Mellanox ConnectX-3 series badly uses the 128 sub class
# Check for the specific known boards with bad subclass.
#
# Concerned devices are:
# 15b3:1003 MT27500 Family [ConnectX-3]
# 15b3:1004 MT27500/MT27520 Family [ConnectX-3/ConnectX-3 Pro Virtual Function]
# 15b3:1007 MT27520 Family [ConnectX-3 Pro]
if hwdevice["vendor_id"] == 71_091 && [69_635, 69_636,
69_639].include?(hwdevice["device_id"])
return "ib"
end
# Nothing was found
Builtins.y2error("Unknown network controller type: %1", hwdevice)
Builtins.y2error(
"It's probably missing in hwinfo (NOT src/hd/hd.h:sc_net_if)"
)
return ""
else
# Nothing was found
Builtins.y2error("Unknown network controller type: %1", hwdevice)
return ""
end
end
# exception for infiniband device
return "ib" if Ops.get_integer(hwdevice, "class_id", -1) == 12 && (subclass_id == 6)
# Communication controller
case Ops.get_integer(hwdevice, "class_id", -1)
when 7
case subclass_id
when 3
return "modem"
when 128
# Nothing was found
Builtins.y2error("Unknown network controller type: %1", hwdevice)
Builtins.y2error(
"It's probably missing in hwinfo (src/hd/hd.h:sc_net_if)"
)
return ""
else
# Nothing was found
Builtins.y2error("Unknown network controller type: %1", hwdevice)
return ""
end
# Network Interface
# check the CVS history and then kill this code!
# 0x107 is the output of hwinfo --network
# which lists the INTERFACES
# but we are inteested in hwinfo --netcard
# Just make sure that hwinfo or ag_probe
# indeed does not pass this to us
when 263
Builtins.y2milestone("CLASS 0x107") # this should happen rarely
case subclass_id
when 0
return "lo"
when 1
return "eth"
when 2
return "tr"
when 3
return "fddi"
when 4
return "ctc"
when 5
return "iucv"
when 6
return "hsi"
when 7
return "qeth"
when 8
return "escon"
when 9
return "myri"
when 10
return "wlan"
when 11
return "xp"
when 12
return "usb"
when 128
# Nothing was found
Builtins.y2error("Unknown network interface type: %1", hwdevice)
Builtins.y2error(
"It's probably missing in hwinfo (src/hd/hd.h:sc_net_if)"
)
return ""
when 129
return "sit"
else
# Nothing was found
Builtins.y2error("Unknown network interface type: %1", hwdevice)
return ""
end
when 258
return "modem"
when 259
return "isdn"
when 276
return "dsl"
end
# Nothing was found
Builtins.y2error("Unknown controller type: %1", hwdevice)
""
end
# Read HW information
# @param [String] hwtype type of devices to read (netcard|modem|isdn)
# @return array of hashes describing detected device
def ReadHardware(hwtype)
hardware = []
Builtins.y2debug("hwtype=%1", hwtype)
# Confirmation: label text (detecting hardware: xxx)
return [] if !confirmed_detection(hwtype)
# read the corresponding hardware
allcards = []
if hwtypes[hwtype]
allcards = Convert.to_list(SCR.Read(hwtypes[hwtype]))
elsif hwtype == "all" || hwtype.nil? || hwtype.empty?
Builtins.maplist(
Convert.convert(
Map.Values(hwtypes),
from: "list",
to: "list <path>"
)
) do |v|
allcards = Builtins.merge(allcards, Convert.to_list(SCR.Read(v)))
end
else
Builtins.y2error("unknown hwtype: %1", hwtype)
return []
end
if allcards.nil?
Builtins.y2error("hardware detection failure")
return []
end
# #97540
bms = Convert.to_string(SCR.Read(path(".etc.install_inf.BrokenModules")))
bms = "" if bms.nil?
broken_modules = Builtins.splitstring(bms, " ")
# fill in the hardware data
num = 0
Builtins.maplist(
Convert.convert(allcards, from: "list", to: "list <map>")
) do |card|
# common stuff
resource = Ops.get_map(card, "resource", {})
controller = ControllerType(card)
one = {}
one["name"] = DeviceName(card)
# Temporary solution for s390: #40587
one["name"] = DistinguishedName(one["name"], card) if Arch.s390
one["type"] = controller
one["udi"] = card["udi"] || ""
one["sysfs_id"] = card["sysfs_id"] || ""
one["dev_name"] = card["dev_name"] || ""
one["requires"] = card["requires"] || []
one["modalias"] = card["modalias"] || ""
one["unique"] = card["unique_key"] || ""
# driver option needs for (bnc#412248)
one["driver"] = card["driver"] || ""
# Each card remembers its position in the list of _all_ cards.
# It is used when selecting the card from the list of _unconfigured_
# ones (which may be smaller). #102945.
one["num"] = num
case controller
# modem
when "modem"
one["device_name"] = card["dev_name"] || ""
one["drivers"] = card["drivers"] || []
speed = Ops.get_integer(resource, ["baud", 0, "speed"], 57_600)
# :-) have to check .probe and libhd if this confusion is
# really necessary. maybe a pppd bug too? #148893
speed = 57_600 if speed == 12_000_000
one["speed"] = speed
one["init1"] = Ops.get_string(resource, ["init_strings", 0, "init1"], "")
one["init2"] = Ops.get_string(resource, ["init_strings", 0, "init2"], "")
one["pppd_options"] = Ops.get_string(resource, ["pppd_option", 0, "option"], "")
# isdn card
when "isdn"
drivers = card["isdn"] || []
one["drivers"] = drivers
one["sel_drv"] = 0
one["bus"] = card["bus"] || ""
one["io"] = Ops.get_integer(resource, ["io", 0, "start"], 0)
one["irq"] = Ops.get_integer(resource, ["irq", 0, "irq"], 0)
# dsl card
when "dsl"
driver_info = Ops.get_map(card, ["dsl", 0], {})
translate_mode = { "capiadsl" => "capi-adsl", "pppoe" => "pppoe" }
m = driver_info["mode"] || ""
one["pppmode"] = translate_mode[m] || m
# treat the rest as a network card
else
# drivers:
# Although normally there is only one module
# (one=$[active:, module:, options:,...]), the generic
# situation is: one or more driver variants (exclusive),
# each having one or more modules (one[drivers])
# only drivers that are not marked as broken (#97540)
drivers = Builtins.filter(Ops.get_list(card, "drivers", [])) do |d|
# ignoring more modules per driver...
module0 = Ops.get_list(d, ["modules", 0], []) # [module, options]
brk = broken_modules.include?(module0[0])
Builtins.y2milestone("In BrokenModules, skipping: %1", module0) if brk
!brk
end
if drivers == []
Builtins.y2milestone("No good drivers found")
else
one["drivers"] = drivers
driver = drivers[0] || {}
one["active"] = driver["active"] || false
module0 = Ops.get_list(driver, ["modules", 0], [])
one["module"] = module0[0] || ""
one["options"] = module0[1] || ""
end
# FIXME: this should be also done for modems and others
# FIXME: #13571
hp = card["hotplug"] || ""
case hp
when "pcmcia", "cardbus"
one["hotplug"] = "pcmcia"
when "usb"
one["hotplug"] = "usb"
end
# store the BUS type
bus = card["bus_hwcfg"] || card["bus"] || ""
case bus
when "PCI"
bus = "pci"
when "USB"
bus = "usb"
when "Virtual IO"
bus = "vio"
end
one["bus"] = bus
one["busid"] = card["sysfs_bus_id"] || ""
if one["busid"].start_with?("virtio")
one["sub_device_busid"] = one["busid"]
one["busid"] = one["sysfs_id"].split("/")[-2]
end
one["mac"] = Ops.get_string(resource, ["hwaddr", 0, "addr"], "")
one["permanent_mac"] = Ops.get_string(resource, ["phwaddr", 0, "addr"], "")
# is the cable plugged in? nil = don't know
one["link"] = Ops.get(resource, ["link", 0, "state"])
# Wireless Card Features
one["wl_channels"] = Ops.get(resource, ["wlan", 0, "channels"])
one["wl_bitrates"] = Ops.get(resource, ["wlan", 0, "bitrates"])
one["wl_auth_modes"] = Ops.get(resource, ["wlan", 0, "auth_modes"])
one["wl_enc_modes"] = Ops.get(resource, ["wlan", 0, "enc_modes"])
end
if controller != "" && !filter_out(card, one["module"])
Builtins.y2debug("found device: %1", one)
Ops.set(hardware, Builtins.size(hardware), one)
num += 1
else
Builtins.y2milestone("Filtering out: %1", card)
end
end
# if there is wlan, put it to the front of the list
# that's because we want it proposed and currently only one card
# can be proposed
found = false
i = 0
Builtins.foreach(hardware) do |h|
if h["type"] == "wlan"
found = true
raise Break
end
i += 1
end
if found
temp = hardware[0] || {}
hardware[0] = hardware[i]
hardware[i] = temp
# adjust mapping: #98852, #102945
Ops.set(hardware, [0, "num"], 0)
Ops.set(hardware, [i, "num"], i)
end
Builtins.y2debug("Hardware=%1", hardware)
deep_copy(hardware)
end
# Run an external command on the target machine and check if it was
# successful. Remember to always use a full path for the command and to
# quote the arguments. Using shellescape() is recommended.
#
# @param command [String] Shell command to run
# @return whether command execution succeeds
def Run(command)
if !command.lstrip.start_with?("/")
log.warn("Command does not have an absolute path: #{command}")
end
ret = SCR.Execute(path(".target.bash"), command).zero?
Builtins.y2error("Run <%1>: Command execution failed.", command) if !ret
ret
end
# TODO: end
# Wrapper to call 'ip link set up' with the given interface
#
# @param dev_name [String] name of interface to 'set link up'
def SetLinkUp(dev_name)
log.info("Setting link up for interface #{dev_name}")
Run("/sbin/ip link set #{dev_name.shellescape} up")
end
# Checks if given device has carrier
#
# @return [boolean] true if device has carrier
def carrier?(dev_name)
SCR.Read(
path(".target.string"),
"/sys/class/net/#{dev_name}/carrier"
).to_i != 0
end
# With NPAR and SR-IOV capabilities, one device could divide a ethernet
# port in various. If the driver module support it, we can check the phys
# port id via sysfs reading the /sys/class/net/$dev_name/phys_port_id
#
# @param dev_name [String] device name to check
# @return [String] physical port id if supported or a empty string if not
def physical_port_id(dev_name)
SCR.Read(
path(".target.string"),
"/sys/class/net/#{dev_name}/phys_port_id"
).to_s.strip
end
# @return [boolean] true if the physical port id is not empty
# @see #physical_port_id
def physical_port_id?(dev_name)
!physical_port_id(dev_name).empty?
end
# Dev port of the given interface from /sys/class/net/$dev_name/dev_port
#
# @param dev_name [String] device name to check
# @return [String] dev port or an empty string if not
def dev_port(dev_name)
SCR.Read(
path(".target.string"),
"/sys/class/net/#{dev_name}/dev_port"
).to_s.strip
end
# Checks if device is physically connected to a network
#
# It does neccessary steps which might be needed for proper initialization
# of devices driver.
#
# @return [boolean] true if physical layer is connected
def phy_connected?(dev_name)
return true if carrier?(dev_name)
# SetLinkUp ensures that driver is loaded
SetLinkUp(dev_name)
# Wait for driver initialization if needed. bnc#876848
# 5 secs is minimum proposed by sysconfig guys for intel drivers.
#
# For a discussion regarding this see
# https://github.com/yast/yast-network/pull/202
sleep(5)
carrier?(dev_name)
end
def unconfigureable_service?
Yast.import "Lan"
return true if Mode.normal && Lan.yast_config&.backend?(:network_manager)
return true unless Lan.yast_config&.backend
false
end
# Disables all widgets which cannot be configured with current network service
#
# see bnc#433084
# if listed any items, disable them, if show_popup, show warning popup
#
# returns true if items were disabled
def disable_unconfigureable_items(items, show_popup)
return false if !unconfigureable_service?
items.each { |i| UI.ChangeWidget(Id(i), :Enabled, false) }
if show_popup
Popup.Warning(
_(
"Network is currently handled by NetworkManager\n" \
"or completely disabled. YaST is unable to configure some options."
)
)
UI.FakeUserInput("ID" => "global")
end
true
end
private
# Checks if the device should be filtered out in ReadHardware
def filter_out(device_info, driver)
# filter out device with virtio_pci Driver and no Device File (bnc#585506)
if driver == "virtio_pci" && (device_info["dev_name"] || "") == ""
log.info("Filtering out virtio device without device file.")
return true
end
# filter out device with chelsio Driver and no Device File or which cannot
# networking (bnc#711432)
if (driver == "cxgb4" &&
device_info["dev_name"].to_s.empty?) ||
(device_info["vendor_id"] == 70_693 &&
device_info["device_id"] == 82_178)
log.info("Filtering out Chelsio device without device file.")
return true
end
if device_info["device"] == "IUCV" && device_info["sysfs_bus_id"] != "netiucv"
# exception to filter out uicv devices (bnc#585363)
log.info("Filtering out iucv device different from netiucv.")
return true
end
if device_info["storageonly"]
# This is for broadcoms multifunctional devices. bnc#841170
log.info("Filtering out device with storage only flag")
return true
end
false
end
# Device type probe paths.
def hwtypes
{
"netcard" => path(".probe.netcard"),
"modem" => path(".probe.modem"),
"isdn" => path(".probe.isdn"),
"dsl" => path(".probe.dsl")
}
end
# If the user requested manual installation, ask whether to probe hardware of this type
def confirmed_detection(hwtype)
# Device type labels.
hwstrings = {
# Confirmation: label text (detecting hardware: xxx)
"netcard" => _(
"Network Cards"
),
# Confirmation: label text (detecting hardware: xxx)
"modem" => _(
"Modems"
),
# Confirmation: label text (detecting hardware: xxx)
"isdn" => _(
"ISDN Cards"
),
# Confirmation: label text (detecting hardware: xxx)
"dsl" => _(
"DSL Devices"
)
}
hwstring = hwstrings[hwtype] || _("All Network Devices")
Confirm.Detection(hwstring, nil)
end
# Returns a generic message informing user that incorrect DHCLIENT_SET_HOSTNAME
# setup was detected.
#
# @param cfgs [Array<String>] list of incorrectly configured devices
# @return [String] a message stating that incorrect DHCLIENT_SET_HOSTNAME setup was detected
def fix_dhclient_msg(cfgs)
format(
_(
"More than one interface asks to control the hostname via DHCP.\n" \
"If you keep the current settings, the behavior is non-deterministic.\n\n" \
"Involved configuration files:\n" \
"%s\n"
),
cfgs.join(" ")
)
end
# A popup informing user that incorrent DHCLIENT_SET_HOSTNAME was detected
#
# @param devs [Array<String>] list of incorrectly configured devices
# @return [void]
def fix_dhclient_warning(devs)
Report.Warning(fix_dhclient_msg(devs))
end
end
end