mirror of
https://github.com/siderolabs/talos.git
synced 2025-10-02 03:01:15 +02:00
See https://go.dev/doc/go1.19 Signed-off-by: Andrey Smirnov <andrey.smirnov@talos-systems.com>
444 lines
12 KiB
Go
444 lines
12 KiB
Go
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
package network
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/talos-systems/go-procfs/procfs"
|
|
"inet.af/netaddr"
|
|
|
|
"github.com/talos-systems/talos/pkg/machinery/config/types/v1alpha1"
|
|
"github.com/talos-systems/talos/pkg/machinery/constants"
|
|
"github.com/talos-systems/talos/pkg/machinery/nethelpers"
|
|
"github.com/talos-systems/talos/pkg/machinery/ordered"
|
|
"github.com/talos-systems/talos/pkg/machinery/resources/network"
|
|
)
|
|
|
|
// CmdlineNetworking contains parsed cmdline networking settings.
|
|
type CmdlineNetworking struct {
|
|
DHCP bool
|
|
Address netaddr.IPPrefix
|
|
Gateway netaddr.IP
|
|
Hostname string
|
|
LinkName string
|
|
DNSAddresses []netaddr.IP
|
|
NTPAddresses []netaddr.IP
|
|
IgnoreInterfaces []string
|
|
NetworkLinkSpecs []network.LinkSpecSpec
|
|
}
|
|
|
|
// splitIPArgument splits the `ip=` kernel argument honoring the IPv6 addresses in square brackets.
|
|
func splitIPArgument(val string) []string {
|
|
var (
|
|
squared, prev int
|
|
parts []string
|
|
)
|
|
|
|
for i, c := range val {
|
|
switch c {
|
|
case '[':
|
|
squared++
|
|
case ']':
|
|
squared--
|
|
case ':':
|
|
if squared != 0 {
|
|
continue
|
|
}
|
|
|
|
parts = append(parts, strings.Trim(val[prev:i], "[]"))
|
|
prev = i + 1
|
|
}
|
|
}
|
|
|
|
parts = append(parts, strings.Trim(val[prev:], "[]"))
|
|
|
|
return parts
|
|
}
|
|
|
|
// ParseCmdlineNetwork parses `ip=` and Talos specific kernel cmdline argument producing all the available configuration options.
|
|
//
|
|
//nolint:gocyclo,cyclop
|
|
func ParseCmdlineNetwork(cmdline *procfs.Cmdline) (CmdlineNetworking, error) {
|
|
var (
|
|
settings CmdlineNetworking
|
|
err error
|
|
linkSpecSpecs []network.LinkSpecSpec
|
|
)
|
|
|
|
// process Talos specific kernel params
|
|
cmdlineHostname := cmdline.Get(constants.KernelParamHostname).First()
|
|
if cmdlineHostname != nil {
|
|
settings.Hostname = *cmdlineHostname
|
|
}
|
|
|
|
ignoreInterfaces := cmdline.Get(constants.KernelParamNetworkInterfaceIgnore)
|
|
for i := 0; ignoreInterfaces.Get(i) != nil; i++ {
|
|
settings.IgnoreInterfaces = append(settings.IgnoreInterfaces, *ignoreInterfaces.Get(i))
|
|
}
|
|
|
|
// standard ip=
|
|
ipSettings := cmdline.Get("ip").First()
|
|
|
|
// dracut bond=
|
|
// ref: https://man7.org/linux/man-pages/man7/dracut.cmdline.7.html
|
|
bondSettings := cmdline.Get(constants.KernelParamBonding).First()
|
|
|
|
if ipSettings != nil {
|
|
// https://www.kernel.org/doc/Documentation/filesystems/nfs/nfsroot.txt
|
|
// ip=<client-ip>:<server-ip>:<gw-ip>:<netmask>:<hostname>:<device>:<autoconf>:<dns0-ip>:<dns1-ip>:<ntp0-ip>
|
|
fields := splitIPArgument(*ipSettings)
|
|
|
|
// If dhcp is specified, we'll handle it as a normal discovered
|
|
// interface
|
|
if len(fields) == 1 && fields[0] == "dhcp" {
|
|
settings.DHCP = true
|
|
}
|
|
|
|
if !settings.DHCP {
|
|
for i := range fields {
|
|
if fields[i] == "" {
|
|
continue
|
|
}
|
|
|
|
switch i {
|
|
case 0:
|
|
var ip netaddr.IP
|
|
|
|
ip, err = netaddr.ParseIP(fields[0])
|
|
if err != nil {
|
|
return settings, fmt.Errorf("cmdline address parse failure: %s", err)
|
|
}
|
|
|
|
// default is to have complete address masked
|
|
settings.Address = netaddr.IPPrefixFrom(ip, ip.BitLen())
|
|
case 2:
|
|
settings.Gateway, err = netaddr.ParseIP(fields[2])
|
|
if err != nil {
|
|
return settings, fmt.Errorf("cmdline gateway parse failure: %s", err)
|
|
}
|
|
case 3:
|
|
var netmask netaddr.IP
|
|
|
|
netmask, err = netaddr.ParseIP(fields[3])
|
|
if err != nil {
|
|
return settings, fmt.Errorf("cmdline netmask parse failure: %s", err)
|
|
}
|
|
|
|
ones, _ := net.IPMask(netmask.IPAddr().IP).Size()
|
|
|
|
settings.Address = netaddr.IPPrefixFrom(settings.Address.IP(), uint8(ones))
|
|
case 4:
|
|
if settings.Hostname == "" {
|
|
settings.Hostname = fields[4]
|
|
}
|
|
case 5:
|
|
settings.LinkName = fields[5]
|
|
case 7, 8:
|
|
var dnsIP netaddr.IP
|
|
|
|
dnsIP, err = netaddr.ParseIP(fields[i])
|
|
if err != nil {
|
|
return settings, fmt.Errorf("error parsing DNS IP: %w", err)
|
|
}
|
|
|
|
settings.DNSAddresses = append(settings.DNSAddresses, dnsIP)
|
|
case 9:
|
|
var ntpIP netaddr.IP
|
|
|
|
ntpIP, err = netaddr.ParseIP(fields[i])
|
|
if err != nil {
|
|
return settings, fmt.Errorf("error parsing DNS IP: %w", err)
|
|
}
|
|
|
|
settings.NTPAddresses = append(settings.NTPAddresses, ntpIP)
|
|
}
|
|
}
|
|
}
|
|
|
|
// if interface name is not set, pick the first non-loopback interface
|
|
if settings.LinkName == "" {
|
|
ifaces, _ := net.Interfaces() //nolint:errcheck // ignoring error here as ifaces will be empty
|
|
|
|
sort.Slice(ifaces, func(i, j int) bool { return ifaces[i].Name < ifaces[j].Name })
|
|
|
|
for _, iface := range ifaces {
|
|
if iface.Flags&net.FlagLoopback != 0 {
|
|
continue
|
|
}
|
|
|
|
settings.LinkName = iface.Name
|
|
|
|
break
|
|
}
|
|
}
|
|
|
|
linkSpecSpecs = append(linkSpecSpecs, network.LinkSpecSpec{
|
|
Name: settings.LinkName,
|
|
Up: true,
|
|
ConfigLayer: network.ConfigCmdline,
|
|
})
|
|
}
|
|
|
|
if bondSettings != nil {
|
|
var (
|
|
bondName, bondMTU string
|
|
bondSlaves []string
|
|
bondOptions v1alpha1.Bond
|
|
)
|
|
|
|
// bond=<bondname>[:<bondslaves>:[:<options>[:<mtu>]]]
|
|
fields := strings.Split(*bondSettings, ":")
|
|
|
|
for i := range fields {
|
|
if fields[i] == "" {
|
|
continue
|
|
}
|
|
|
|
switch i {
|
|
case 0:
|
|
bondName = fields[0]
|
|
case 1:
|
|
bondSlaves = strings.Split(fields[1], ",")
|
|
case 2:
|
|
bondOptions, err = parseBondOptions(fields[2])
|
|
if err != nil {
|
|
return settings, err
|
|
}
|
|
case 3:
|
|
bondMTU = fields[3]
|
|
}
|
|
}
|
|
|
|
// set defaults as per https://man7.org/linux/man-pages/man7/dracut.cmdline.7.html
|
|
// Talos by default sets bond mode to balance-rr
|
|
if bondSlaves == nil {
|
|
bondSlaves = []string{
|
|
"eth0",
|
|
"eth1",
|
|
}
|
|
}
|
|
|
|
bondLinkSpec := network.LinkSpecSpec{
|
|
Name: bondName,
|
|
Up: true,
|
|
ConfigLayer: network.ConfigCmdline,
|
|
}
|
|
|
|
if bondMTU != "" {
|
|
mtu, err := strconv.Atoi(bondMTU)
|
|
if err != nil {
|
|
return settings, fmt.Errorf("error parsing bond MTU: %w", err)
|
|
}
|
|
|
|
bondLinkSpec.MTU = uint32(mtu)
|
|
}
|
|
|
|
if err := SetBondMaster(&bondLinkSpec, &bondOptions); err != nil {
|
|
return settings, fmt.Errorf("error setting bond master: %w", err)
|
|
}
|
|
|
|
linkSpecSpecs = append(linkSpecSpecs, bondLinkSpec)
|
|
|
|
for idx, slave := range bondSlaves {
|
|
slaveLinkSpec := network.LinkSpecSpec{
|
|
Name: slave,
|
|
Up: true,
|
|
ConfigLayer: network.ConfigCmdline,
|
|
}
|
|
SetBondSlave(&slaveLinkSpec, ordered.MakePair(bondName, idx))
|
|
linkSpecSpecs = append(linkSpecSpecs, slaveLinkSpec)
|
|
}
|
|
}
|
|
// dracut vlan=<vlanname>:<phydevice>
|
|
vlanSettings := cmdline.Get(constants.KernelParamVlan).First()
|
|
if vlanSettings != nil {
|
|
vlanName, phyDevice, ok := strings.Cut(*vlanSettings, ":")
|
|
if !ok {
|
|
return settings, fmt.Errorf("malformed vlan commandline argument: %s", *vlanSettings)
|
|
}
|
|
|
|
_, vlanNumberString, ok := strings.Cut(vlanName, ".")
|
|
if !ok {
|
|
return settings, fmt.Errorf("malformed vlan commandline argument: %s", *vlanSettings)
|
|
}
|
|
|
|
vlanID, err := strconv.Atoi(vlanNumberString)
|
|
|
|
if err != nil || vlanNumberString == "" {
|
|
return settings, errors.New("unable to parse vlan")
|
|
}
|
|
|
|
vlanSpec := network.VLANSpec{
|
|
VID: uint16(vlanID),
|
|
Protocol: nethelpers.VLANProtocol8021Q,
|
|
}
|
|
|
|
vlanName = fmt.Sprintf("%s.%d", phyDevice, vlanID)
|
|
|
|
linkSpecUpdated := false
|
|
|
|
for i, linkSpec := range linkSpecSpecs {
|
|
if linkSpec.Name == vlanName {
|
|
vlanLink(&linkSpecSpecs[i], phyDevice, vlanSpec)
|
|
|
|
linkSpecUpdated = true
|
|
|
|
break
|
|
}
|
|
}
|
|
|
|
if !linkSpecUpdated {
|
|
linkSpec := network.LinkSpecSpec{
|
|
Name: vlanName,
|
|
Up: true,
|
|
ConfigLayer: network.ConfigCmdline,
|
|
}
|
|
|
|
vlanLink(&linkSpec, phyDevice, vlanSpec)
|
|
|
|
linkSpecSpecs = append(linkSpecSpecs, linkSpec)
|
|
}
|
|
}
|
|
|
|
settings.NetworkLinkSpecs = linkSpecSpecs
|
|
|
|
return settings, nil
|
|
}
|
|
|
|
// parseBondOptions parses the options string into v1alpha1.Bond
|
|
// v1alpha1.Bond was chosen to re-use the `SetBondMaster` and `SetBondSlave` functions
|
|
// ref: modinfo bonding
|
|
//
|
|
//nolint:gocyclo,cyclop
|
|
func parseBondOptions(options string) (v1alpha1.Bond, error) {
|
|
var bond v1alpha1.Bond
|
|
|
|
bondOptions := strings.Split(options, ",")
|
|
|
|
for _, opt := range bondOptions {
|
|
optionPair := strings.Split(opt, "=")
|
|
|
|
switch optionPair[0] {
|
|
case "arp_ip_target":
|
|
bond.BondARPIPTarget = strings.Split(optionPair[1], ";")
|
|
case "mode":
|
|
bond.BondMode = optionPair[1]
|
|
case "xmit_hash_policy":
|
|
bond.BondHashPolicy = optionPair[1]
|
|
case "lacp_rate":
|
|
bond.BondLACPRate = optionPair[1]
|
|
case "arp_validate":
|
|
bond.BondARPValidate = optionPair[1]
|
|
case "arp_all_targets":
|
|
bond.BondARPAllTargets = optionPair[1]
|
|
case "primary":
|
|
bond.BondPrimary = optionPair[1]
|
|
case "primary_reselect":
|
|
bond.BondPrimaryReselect = optionPair[1]
|
|
case "fail_over_mac":
|
|
bond.BondFailOverMac = optionPair[1]
|
|
case "ad_select":
|
|
bond.BondADSelect = optionPair[1]
|
|
case "miimon":
|
|
miimon, err := strconv.Atoi(optionPair[1])
|
|
if err != nil {
|
|
return bond, fmt.Errorf("error parsing bond option miimon: %w", err)
|
|
}
|
|
|
|
bond.BondMIIMon = uint32(miimon)
|
|
case "updelay":
|
|
updelay, err := strconv.Atoi(optionPair[1])
|
|
if err != nil {
|
|
return bond, fmt.Errorf("error parsing bond option updelay: %w", err)
|
|
}
|
|
|
|
bond.BondUpDelay = uint32(updelay)
|
|
case "downdelay":
|
|
downdelay, err := strconv.Atoi(optionPair[1])
|
|
if err != nil {
|
|
return bond, fmt.Errorf("error parsing bond option downdelay: %w", err)
|
|
}
|
|
|
|
bond.BondDownDelay = uint32(downdelay)
|
|
case "arp_interval":
|
|
arpInterval, err := strconv.Atoi(optionPair[1])
|
|
if err != nil {
|
|
return bond, fmt.Errorf("error parsing bond option arp_interval: %w", err)
|
|
}
|
|
|
|
bond.BondARPInterval = uint32(arpInterval)
|
|
case "resend_igmp":
|
|
resendIGMP, err := strconv.Atoi(optionPair[1])
|
|
if err != nil {
|
|
return bond, fmt.Errorf("error parsing bond option resend_igmp: %w", err)
|
|
}
|
|
|
|
bond.BondResendIGMP = uint32(resendIGMP)
|
|
case "min_links":
|
|
minLinks, err := strconv.Atoi(optionPair[1])
|
|
if err != nil {
|
|
return bond, fmt.Errorf("error parsing bond option min_links: %w", err)
|
|
}
|
|
|
|
bond.BondMinLinks = uint32(minLinks)
|
|
case "lp_interval":
|
|
lpInterval, err := strconv.Atoi(optionPair[1])
|
|
if err != nil {
|
|
return bond, fmt.Errorf("error parsing bond option lp_interval: %w", err)
|
|
}
|
|
|
|
bond.BondLPInterval = uint32(lpInterval)
|
|
case "packets_per_slave":
|
|
packetsPerSlave, err := strconv.Atoi(optionPair[1])
|
|
if err != nil {
|
|
return bond, fmt.Errorf("error parsing bond option packets_per_slave: %w", err)
|
|
}
|
|
|
|
bond.BondPacketsPerSlave = uint32(packetsPerSlave)
|
|
case "num_grat_arp":
|
|
numGratArp, err := strconv.Atoi(optionPair[1])
|
|
if err != nil {
|
|
return bond, fmt.Errorf("error parsing bond option num_grat_arp: %w", err)
|
|
}
|
|
|
|
bond.BondNumPeerNotif = uint8(numGratArp)
|
|
case "num_unsol_na":
|
|
numGratArp, err := strconv.Atoi(optionPair[1])
|
|
if err != nil {
|
|
return bond, fmt.Errorf("error parsing bond option num_unsol_na: %w", err)
|
|
}
|
|
|
|
bond.BondNumPeerNotif = uint8(numGratArp)
|
|
case "all_slaves_active":
|
|
allSlavesActive, err := strconv.Atoi(optionPair[1])
|
|
if err != nil {
|
|
return bond, fmt.Errorf("error parsing bond option all_slaves_active: %w", err)
|
|
}
|
|
|
|
bond.BondAllSlavesActive = uint8(allSlavesActive)
|
|
case "use_carrier":
|
|
useCarrier, err := strconv.Atoi(optionPair[1])
|
|
if err != nil {
|
|
return bond, fmt.Errorf("error parsing bond option use_carrier: %w", err)
|
|
}
|
|
|
|
if useCarrier == 1 {
|
|
val := []bool{true}
|
|
bond.BondUseCarrier = &val[0]
|
|
}
|
|
default:
|
|
return bond, fmt.Errorf("unknown bond option: %s", optionPair[0])
|
|
}
|
|
}
|
|
|
|
return bond, nil
|
|
}
|