666 lines
25 KiB
Bash
Executable file
666 lines
25 KiB
Bash
Executable file
#!/bin/sh
|
|
|
|
# HCN (Hybrid Cloud Network) dracut module
|
|
# Discovers HCN configurations from device-tree and sets up bonding
|
|
#
|
|
# Workflow:
|
|
# 1. Check if HCN is enabled (rd.hcn=1, rd.hcn.ip, or rd.hcn.route), exit early if not
|
|
# 2. Discover HCN devices from device-tree (/proc/device-tree)
|
|
# - Scans PCI devices (SR-IOV ethernet adapters)
|
|
# - Scans vdevices (VNIC and Virtual Ethernet)
|
|
# 3. Build MAPPINGS: bond names -> slave devices with MACs and modes
|
|
# 4. Process rd.hcn.ip= and rd.hcn.route= parameters and replace slave references with bonds
|
|
# 5. Carry over the remaining network options of the real kernel command line
|
|
# (nameserver=, rd.peerdns=, rd.net.dhcp.*)
|
|
# 6. Generate NetworkManager connections to a temporary directory via nm-initrd-generator
|
|
# 7. Fix up generated connections to use correct bond masters and naming
|
|
# 8. Copy connections to /etc/NetworkManager/system-connections for persistence
|
|
#
|
|
# This script runs from hcn-init-initrd.service, once udev has discovered the
|
|
# devices. Much earlier, hcn-cmdline.sh has already told NetworkManager and the
|
|
# other dracut modules to leave the network alone by writing an "ip=hcn" marker
|
|
# to /etc/cmdline.d, so nothing configures the bond ports behind our back.
|
|
#
|
|
# Usage:
|
|
# rd.hcn=1 - Create bond connections only (no IP configuration)
|
|
# rd.hcn.ip=<config> - Create bonds with HCN-specific IP configuration
|
|
# rd.hcn.route=<config> - Create bonds with HCN-specific route configuration
|
|
|
|
command -v getargs >/dev/null || . /lib/dracut-lib.sh
|
|
|
|
# Helper to read 4 bytes from device-tree and return hex string
|
|
xdump4() {
|
|
hexdump -n 4 -ve '/1 "%02x"' "$1"
|
|
}
|
|
|
|
# Helper to get MAC address from device-tree
|
|
get_mac() {
|
|
local dev=$1
|
|
if [ -f "$dev/local-mac-address" ]; then
|
|
hexdump -ve '/1 "%02x:"' "$dev/local-mac-address" | sed 's/:$//'
|
|
fi
|
|
}
|
|
|
|
# Number of 3 second waits left for the devices to show up in sysfs after a
|
|
# migration, 3 minutes in total.
|
|
#
|
|
# The budget is global and not per device on purpose: the devices appear in
|
|
# parallel, so waiting for them one by one would multiply the time spent here
|
|
# by the number of devices and hcn-init-initrd.service, which allows 5 minutes
|
|
# for the whole run, would kill the script in the middle of it.
|
|
HCN_WAIT_RETRIES=60
|
|
|
|
# Function to discover HCN mapping for a device-tree node
|
|
#
|
|
# On success it sets HCN_MAPPING to "bondname devname mac mode" and returns 0.
|
|
# The result is handed over in a variable instead of being printed because the
|
|
# function logs its progress with info(), and under systemd info() writes to
|
|
# stdout, which would end up mixed into the mapping (see carry_over_cmdline).
|
|
get_dev_hcn() {
|
|
local dev=$1
|
|
local hcnid devname mode mac ofpath
|
|
|
|
HCN_MAPPING=""
|
|
|
|
hcnid=$(xdump4 "$dev/ibm,hcn-id")
|
|
[ -z "$hcnid" ] && return 1
|
|
|
|
mode=$(tr -d '\0' <"$dev/ibm,hcn-mode" 2>/dev/null)
|
|
ofpath=${dev#/proc/device-tree}
|
|
|
|
# Wait for device to appear in sysfs. This might take time after migration.
|
|
# Every device is looked up at least once, even with the shared wait budget
|
|
# already spent by the previous ones. devname is reset on each try because
|
|
# ofpathname can resolve a name for a device that is not in sysfs yet, and
|
|
# such a name must not be taken as a valid result once the waiting is over.
|
|
while :; do
|
|
devname=$(ofpathname -l "$ofpath" 2>/dev/null)
|
|
if [ -n "$devname" ] && [ -e "/sys/class/net/$devname" ]; then
|
|
info "parse-hcn: device $devname ready for $ofpath"
|
|
mac=$(get_mac "$dev")
|
|
break
|
|
fi
|
|
devname=""
|
|
[ "${HCN_WAIT_RETRIES:-0}" -gt 0 ] || break
|
|
# The tries are short so that a device that is already there is not waited
|
|
# for longer than needed, but only one out of five is logged, the messages
|
|
# also go to the console
|
|
if [ $((HCN_WAIT_RETRIES % 5)) -eq 0 ]; then
|
|
info "parse-hcn: waiting for device for $ofpath ($HCN_WAIT_RETRIES tries left)"
|
|
fi
|
|
sleep 3
|
|
HCN_WAIT_RETRIES=$((HCN_WAIT_RETRIES - 1))
|
|
done
|
|
|
|
if [ -z "$devname" ]; then
|
|
warn "parse-hcn: could not resolve device name for $dev"
|
|
return 1
|
|
fi
|
|
|
|
# The bond mapping: bondname devname mac mode
|
|
HCN_MAPPING="bond$hcnid $devname ${mac:-none} ${mode:-none}"
|
|
return 0
|
|
}
|
|
|
|
# Get a value from a keyfile (INI format)
|
|
# Usage: gkeyfile_get <file> <section> <key>
|
|
gkeyfile_get() {
|
|
awk -v sec="$2" -v key="$3" '
|
|
$0 ~ "^[[:space:]]*\\[.*\\][[:space:]]*$" {
|
|
current_sec = $0
|
|
gsub(/^[[:space:]]+|[[:space:]]+$/, "", current_sec)
|
|
current_sec = substr(current_sec, 2, length(current_sec) - 2)
|
|
in_sec = (current_sec == sec)
|
|
}
|
|
in_sec {
|
|
idx = index($0, "=")
|
|
if (idx > 0) {
|
|
k = substr($0, 1, idx - 1)
|
|
gsub(/^[[:space:]]+|[[:space:]]+$/, "", k)
|
|
if (k == key) {
|
|
val = substr($0, idx + 1)
|
|
gsub(/^[[:space:]]+|[[:space:]]+$|"/, "", val)
|
|
print val
|
|
exit
|
|
}
|
|
}
|
|
}
|
|
' "$1"
|
|
}
|
|
|
|
# Check if a key exists in a keyfile (INI format)
|
|
# Usage: gkeyfile_has <file> <section> <key>
|
|
gkeyfile_has() {
|
|
awk -v sec="$2" -v key="$3" '
|
|
$0 ~ "^[[:space:]]*\\[.*\\][[:space:]]*$" {
|
|
current_sec = $0
|
|
gsub(/^[[:space:]]+|[[:space:]]+$/, "", current_sec)
|
|
current_sec = substr(current_sec, 2, length(current_sec) - 2)
|
|
in_sec = (current_sec == sec)
|
|
}
|
|
in_sec {
|
|
idx = index($0, "=")
|
|
if (idx > 0) {
|
|
k = substr($0, 1, idx - 1)
|
|
gsub(/^[[:space:]]+|[[:space:]]+$/, "", k)
|
|
if (k == key) {
|
|
found = 1
|
|
exit
|
|
}
|
|
}
|
|
}
|
|
END { exit !found }
|
|
' "$1"
|
|
}
|
|
|
|
# Set a value in a keyfile (INI format)
|
|
# Usage: gkeyfile_set <file> <section> <key> <val>
|
|
gkeyfile_set() {
|
|
if gkeyfile_has "$1" "$2" "$3"; then
|
|
sed -i "/^\[$2\]/,/^\[/ s|^\([[:space:]]*$3[[:space:]]*=[[:space:]]*\).*|\1$4|" "$1"
|
|
else
|
|
sed -i "/^\[$2\]/a $3=$4" "$1"
|
|
fi
|
|
}
|
|
|
|
# Parse a NetworkManager connection file and extract key fields
|
|
# Returns: id|uuid|ifname|master|controller|mac (pipe-separated)
|
|
parse_nm_connection() {
|
|
awk -F'=' '
|
|
/^[[:space:]]*id[[:space:]]*=/ {
|
|
gsub(/^[[:space:]]+|[[:space:]]+$|"/, "", $2)
|
|
id = $2
|
|
}
|
|
/^[[:space:]]*uuid[[:space:]]*=/ {
|
|
gsub(/^[[:space:]]+|[[:space:]]+$|"/, "", $2)
|
|
uuid = $2
|
|
}
|
|
/^[[:space:]]*interface-name[[:space:]]*=/ {
|
|
gsub(/^[[:space:]]+|[[:space:]]+$|"/, "", $2)
|
|
ifname = $2
|
|
}
|
|
/^[[:space:]]*master[[:space:]]*=/ {
|
|
gsub(/^[[:space:]]+|[[:space:]]+$|"/, "", $2)
|
|
master = $2
|
|
}
|
|
/^[[:space:]]*controller[[:space:]]*=/ {
|
|
gsub(/^[[:space:]]+|[[:space:]]+$|"/, "", $2)
|
|
controller = $2
|
|
}
|
|
/^[[:space:]]*mac-address[[:space:]]*=/ {
|
|
gsub(/^[[:space:]]+|[[:space:]]+$|:|"/, "", $2)
|
|
mac = tolower($2)
|
|
}
|
|
END {
|
|
print id "|" uuid "|" ifname "|" master "|" controller "|" mac
|
|
}
|
|
' "$1"
|
|
}
|
|
|
|
# Function to fix up NetworkManager connection files
|
|
#
|
|
# IMPORTANT: This function should ONLY be called when rd.hcn=1 is set
|
|
#
|
|
# nm-initrd-generator creates connections with:
|
|
# - Interface names as connection IDs (e.g., "eth0")
|
|
# - UUIDs as master/controller references instead of bond names
|
|
# - Generic interface-name settings
|
|
#
|
|
# We need to transform these to:
|
|
# - Bond-based connection IDs (e.g., "bond0-eth0")
|
|
# - Bond names as master/controller references (e.g., "bond0")
|
|
# - Correct slave-type/port-type set to 'bond'
|
|
# - Match slaves to correct bonds based on MAPPINGS
|
|
#
|
|
# This function only modifies connections that are related to HCN bonds
|
|
# (either bond interfaces themselves or slaves found in MAPPINGS)
|
|
fixup_nm_connections() {
|
|
local conn_dir="${HCN_RUNTIME_CONN_DIR:-$NM_RUNTIME_CONN_DIR}"
|
|
[ -d "$conn_dir" ] || return 0
|
|
|
|
local con id uuid ifname master controller mac uuid_map map
|
|
local found_master found_ifname mapping_info new_id
|
|
|
|
# First pass: build UUID to ID mapping for resolution
|
|
for con in "$conn_dir"/*.nmconnection; do
|
|
[ -e "$con" ] || continue
|
|
IFS='|' read -r id uuid ifname master controller mac <<EOF
|
|
$(parse_nm_connection "$con")
|
|
EOF
|
|
[ -n "$uuid" ] && [ -n "$id" ] && uuid_map="$uuid_map $uuid:$id"
|
|
done
|
|
|
|
# Second pass: fixup files (only those related to HCN bonds)
|
|
for con in "$conn_dir"/*.nmconnection; do
|
|
[ -e "$con" ] || continue
|
|
|
|
# Start from scratch, otherwise a connection that is in no mapping and
|
|
# whose controller is no HCN bond would inherit the values of the previous
|
|
# iteration and be treated as HCN-related
|
|
found_master="" found_ifname=""
|
|
|
|
# Extract connection details
|
|
IFS='|' read -r id uuid ifname master controller mac <<EOF
|
|
$(parse_nm_connection "$con")
|
|
EOF
|
|
|
|
# Guard: Ensure we have at least id and uuid
|
|
if [ -z "$id" ] || [ -z "$uuid" ]; then
|
|
info "parse-hcn: skipping invalid connection file $con"
|
|
continue
|
|
fi
|
|
|
|
# Resolve UUIDs to names for master/controller
|
|
for map in $uuid_map; do
|
|
if [ "$master" = "${map%:*}" ]; then
|
|
master=${map#*:}
|
|
gkeyfile_set "$con" connection master "$master"
|
|
elif [ "$controller" = "${map%:*}" ]; then
|
|
controller=${map#*:}
|
|
gkeyfile_set "$con" connection controller "$controller"
|
|
fi
|
|
done
|
|
|
|
# Fallback if interface-name is missing
|
|
[ -z "$ifname" ] && ifname="$id"
|
|
|
|
# Search MAPPINGS for this connection to find the correct master
|
|
# Only connections in MAPPINGS or with HCN bond masters will be modified
|
|
mapping_info=$(echo "$MAPPINGS" | awk -v iface="$ifname" -v mac="$mac" '
|
|
{
|
|
for (i=1; i<=NF; i+=4) {
|
|
m_bond = $(i)
|
|
m_iface = $(i+1)
|
|
m_mac = $(i+2)
|
|
gsub(/:/, "", m_mac)
|
|
m_mac = tolower(m_mac)
|
|
if ((iface != "" && m_iface == iface) || (mac != "" && m_mac == mac)) {
|
|
print m_bond, m_iface
|
|
exit
|
|
}
|
|
}
|
|
}
|
|
')
|
|
|
|
if [ -n "$mapping_info" ]; then
|
|
found_master=${mapping_info% *}
|
|
found_ifname=${mapping_info#* }
|
|
elif strstr " $BOND_LIST " " ${master:-$controller} "; then
|
|
found_master=${master:-$controller}
|
|
found_ifname=$ifname
|
|
fi
|
|
|
|
# Only modify connections that are HCN-related
|
|
if [ -n "$found_master" ]; then
|
|
info "parse-hcn: fixing up connection $id (HCN-related)"
|
|
|
|
# Ensure controller/master and port-type/slave-type are correct
|
|
if gkeyfile_has "$con" connection controller; then
|
|
gkeyfile_set "$con" connection controller "$found_master"
|
|
elif gkeyfile_has "$con" connection master; then
|
|
gkeyfile_set "$con" connection master "$found_master"
|
|
else
|
|
gkeyfile_set "$con" connection controller "$found_master"
|
|
fi
|
|
|
|
if gkeyfile_has "$con" connection slave-type; then
|
|
gkeyfile_set "$con" connection slave-type bond
|
|
else
|
|
gkeyfile_set "$con" connection port-type bond
|
|
fi
|
|
|
|
# Rename connection ID and file to match expectations
|
|
new_id="$found_master-$found_ifname"
|
|
if [ "$id" != "$new_id" ]; then
|
|
info "parse-hcn: renaming connection $id to $new_id"
|
|
gkeyfile_set "$con" connection id "$new_id"
|
|
mv "$con" "$conn_dir/$new_id.nmconnection"
|
|
fi
|
|
fi
|
|
done
|
|
}
|
|
|
|
# Carry over the rest of the network command line
|
|
#
|
|
# nm-initrd-generator is called with a command line built from scratch, so any
|
|
# network option the user really passed is invisible to it unless it is copied
|
|
# over here. The generator run NetworkManager does on its own is no substitute:
|
|
# the "ip=hcn" marker keeps that run from producing any connection, so
|
|
# everything it would have parsed into one is lost.
|
|
#
|
|
# Options that end up in a global configuration file rather than in a
|
|
# connection (rd.net.dns, rd.net.dns-backend, rd.net.dns-resolve-mode,
|
|
# rd.net.timeout.carrier) are left out on purpose, the NetworkManager run
|
|
# already wrote them to /run/NetworkManager/conf.d.
|
|
#
|
|
# Only the options that name no device are copied. The ones that do
|
|
# (rd.net.dhcp.client-id=, bootdev=, rd.ethtool=, and the vlan=, bridge= and
|
|
# team= stacked devices) would need the port reference rewritten to its bond,
|
|
# and the generator creates a full connection for any device named in them,
|
|
# which this module would then persist to /etc.
|
|
#
|
|
# Appends the options to NEW_ARGS. The result is not printed on purpose: the
|
|
# function logs what it copies with info(), and info() writes to stdout when
|
|
# DRACUT_SYSTEMD is set, so the output of a function using it cannot be
|
|
# captured with a command substitution without getting the log messages mixed
|
|
# into the value.
|
|
#
|
|
# Only options taking a value can be carried over this way, because getargs()
|
|
# prints nothing for an option given as a bare flag. All the ones below do take
|
|
# one, rd.peerdns is used as rd.peerdns=0. Keep that in mind before adding a
|
|
# boolean option here, it would need getargbool() instead.
|
|
carry_over_cmdline() {
|
|
local opt val
|
|
|
|
for opt in nameserver rd.peerdns rd.net.timeout.dhcp rd.net.dhcp.retry \
|
|
rd.net.dhcp.vendor-class rd.net.dhcp.dscp; do
|
|
for val in $(getargs "$opt"); do
|
|
info "parse-hcn: keeping $opt=$val"
|
|
NEW_ARGS="$NEW_ARGS $opt=$val"
|
|
done
|
|
done
|
|
}
|
|
|
|
# --- Main Execution ---
|
|
|
|
info "parse-hcn: starting"
|
|
|
|
MAPPINGS=""
|
|
if [ -d /proc/device-tree ]; then
|
|
# Scan PCI devices (SR-IOV)
|
|
for dev in /proc/device-tree/pci*/ethernet*; do
|
|
[ -e "$dev/ibm,hcn-id" ] || continue
|
|
info "parse-hcn: checking PCI device $dev"
|
|
if get_dev_hcn "$dev"; then
|
|
MAPPINGS="$MAPPINGS $HCN_MAPPING"
|
|
fi
|
|
done
|
|
|
|
# Scan vdevices (VNIC and Virtual Ethernet)
|
|
for dev in /proc/device-tree/vdevice/vnic* /proc/device-tree/vdevice/l-lan*; do
|
|
[ -e "$dev/ibm,hcn-id" ] || continue
|
|
info "parse-hcn: checking vdevice $dev"
|
|
if get_dev_hcn "$dev"; then
|
|
MAPPINGS="$MAPPINGS $HCN_MAPPING"
|
|
fi
|
|
done
|
|
fi
|
|
|
|
if [ -z "$MAPPINGS" ]; then
|
|
info "parse-hcn: no HCN devices found"
|
|
exit 0
|
|
fi
|
|
|
|
info "parse-hcn: discovered mappings: $MAPPINGS"
|
|
|
|
# Command line processing
|
|
NM_CONF_DIR="/etc/NetworkManager"
|
|
NM_CONF_CONN_DIR="$NM_CONF_DIR/system-connections"
|
|
NM_RUNTIME_DIR="/run/NetworkManager"
|
|
NM_RUNTIME_CONN_DIR="$NM_RUNTIME_DIR/system-connections"
|
|
HCN_RUNTIME_DIR="/run/hcn"
|
|
HCN_RUNTIME_CONN_DIR="$HCN_RUNTIME_DIR/system-connections"
|
|
HCN_RUNTIME_CONF_DIR="$HCN_RUNTIME_DIR/conf.d"
|
|
NEW_ARGS=""
|
|
|
|
# Extract unique bond names from discovered mappings
|
|
BOND_NAMES=$(echo "$MAPPINGS" | awk '{for(i=1;i<=NF;i+=4) if (!seen[$i]++) print $i}')
|
|
|
|
# Space separated copy of BOND_NAMES. BOND_NAMES is newline separated, which
|
|
# does not work for the " $list " substring checks done with strstr. The echo
|
|
# is what collapses the newlines, so it is not the useless one it looks like.
|
|
# shellcheck disable=SC2086,SC2116
|
|
BOND_LIST=$(echo $BOND_NAMES)
|
|
|
|
# First discovered bond. Only unqualified *static* configs (a fixed address
|
|
# with no interface field) fall back to this bond, because a fixed address can
|
|
# belong to a single bond. Method-only configs (dhcp, auto6, ...) carry no
|
|
# per-host address and instead fan out to every discovered bond (see below).
|
|
FIRST_BOND=${BOND_LIST%% *}
|
|
|
|
for BONDNAME in $BOND_NAMES; do
|
|
SLAVES="" SLAVE_NAMES="" SLAVE_MACS="" PRIMARY=""
|
|
OTHER_SLAVE_NAMES="" OTHER_SLAVE_MACS=""
|
|
|
|
# Extract slaves for this bond and other bonds from MAPPINGS
|
|
set -- $MAPPINGS
|
|
while [ $# -ge 4 ]; do
|
|
if [ "$1" = "$BONDNAME" ]; then
|
|
SLAVE_NAMES="$SLAVE_NAMES $2"
|
|
[ "$3" != "none" ] && SLAVE_MACS="$SLAVE_MACS $3"
|
|
[ "$4" = "primary" ] && PRIMARY="$2"
|
|
SLAVES="${SLAVES:+$SLAVES,}$2"
|
|
else
|
|
OTHER_SLAVE_NAMES="$OTHER_SLAVE_NAMES $2"
|
|
[ "$3" != "none" ] && OTHER_SLAVE_MACS="$OTHER_SLAVE_MACS $3"
|
|
fi
|
|
shift 4
|
|
done
|
|
|
|
info "parse-hcn: configuring bond $BONDNAME with slaves:$SLAVE_NAMES"
|
|
|
|
# Add bond definition to NEW_ARGS
|
|
BOND_OPTS="mode=1,miimon=100,fail_over_mac=2${PRIMARY:+,primary=$PRIMARY}"
|
|
NEW_ARGS="$NEW_ARGS bond=$BONDNAME:$SLAVES:$BOND_OPTS"
|
|
|
|
# Process rd.hcn.ip= - replace slave names/MACs with bond name
|
|
for HCN_IP in $(getargs rd.hcn.ip); do
|
|
# Lowercased copy for case-insensitive MAC matching: MACs in MAPPINGS are
|
|
# lowercase, but may be supplied in uppercase on the kernel command line.
|
|
HCN_IP_LC=$(echo "$HCN_IP" | tr 'A-Z' 'a-z')
|
|
matched=0
|
|
for slave in $SLAVE_NAMES $SLAVE_MACS; do
|
|
slave_dash=$(str_replace "$slave" ":" "-")
|
|
# MACs (which contain ':') are compared case-insensitively; interface
|
|
# names are compared as-is to preserve their case.
|
|
hcn_cmp=$HCN_IP
|
|
case "$slave" in *:*) hcn_cmp=$HCN_IP_LC ;; esac
|
|
# Check if HCN_IP contains the slave (as a whole word/field)
|
|
if strstr ":$hcn_cmp:" ":$slave:" || strstr ":$hcn_cmp:" ":$slave_dash:"; then
|
|
matched=1
|
|
# Replace slave with bond name (handle boundaries)
|
|
current_hcn_ip=$(echo "$hcn_cmp" | sed -E "s#^($slave|$slave_dash)([: ]|$)#$BONDNAME\2#; s#([: ])($slave|$slave_dash)([: ]|$)#\1$BONDNAME\3#g")
|
|
NEW_ARGS="$NEW_ARGS ip=$current_hcn_ip"
|
|
break
|
|
fi
|
|
done
|
|
|
|
# Check if this IP is targeted at another bond's slave
|
|
matched_other=0
|
|
for slave in $OTHER_SLAVE_NAMES $OTHER_SLAVE_MACS; do
|
|
slave_dash=$(str_replace "$slave" ":" "-")
|
|
hcn_cmp=$HCN_IP
|
|
case "$slave" in *:*) hcn_cmp=$HCN_IP_LC ;; esac
|
|
if strstr ":$hcn_cmp:" ":$slave:" || strstr ":$hcn_cmp:" ":$slave_dash:"; then
|
|
matched_other=1
|
|
break
|
|
fi
|
|
done
|
|
|
|
# No slave matched and no bond name present - apply the unqualified config.
|
|
# Method-only configs (dhcp, auto6, ...) fan out to every bond; unqualified
|
|
# static addresses fall back to the first bond only (see the *) branch).
|
|
has_bond_ip=0
|
|
case "$HCN_IP" in
|
|
*:bond[0-9]*) has_bond_ip=1 ;;
|
|
esac
|
|
if [ $matched -eq 0 ] && [ $matched_other -eq 0 ] && [ $has_bond_ip -eq 0 ]; then
|
|
case "$HCN_IP" in
|
|
dhcp | on | any | single-dhcp | dhcp6 | auto6 | ibft | either6 | link6)
|
|
# Method-only config (no fixed address): each bond obtains its own
|
|
# configuration independently, so apply it to every discovered bond.
|
|
info "parse-hcn: applying $HCN_IP to $BONDNAME"
|
|
NEW_ARGS="$NEW_ARGS ip=$BONDNAME:$HCN_IP"
|
|
;;
|
|
*:dhcp | *:on | *:any | *:dhcp6 | *:auto6 | *:link6)
|
|
# Format: rd.hcn.ip=<interface>:{dhcp|on|any|dhcp6|auto6|link6}[:[<mtu>]]
|
|
# Method-only config with optional MTU: also applies to every bond.
|
|
# Extract optional MTU field (after the method, could be followed by more colons)
|
|
temp_ip=${HCN_IP}
|
|
# Count colons to determine if there are extra fields
|
|
colons=0
|
|
while [ "${temp_ip#*:}" != "$temp_ip" ]; do
|
|
colons=$((colons + 1))
|
|
temp_ip=${temp_ip#*:}
|
|
done
|
|
# Format: interface:method[:mtu]
|
|
# We need at least 1 colon (interface:method), optionally 2 for MTU
|
|
if [ "$colons" -ge 1 ]; then
|
|
info "parse-hcn: applying DHCP/auto config with optional MTU to $BONDNAME"
|
|
NEW_ARGS="$NEW_ARGS ip=$BONDNAME:${HCN_IP#*:}"
|
|
fi
|
|
;;
|
|
*)
|
|
# Static IP configuration: a fixed address can belong to only one bond,
|
|
# so an unqualified static config falls back to the first bond only.
|
|
[ "$BONDNAME" = "$FIRST_BOND" ] || continue
|
|
# Count colons to determine format
|
|
# Standard format: rd.hcn.ip=<client-IP>:[<peer>]:<gateway-IP>:<netmask>:<client_hostname>:<interface>:{none|off|dhcp|on|any|dhcp6|auto6|ibft}[:[<dns1>][:<dns2>]]
|
|
# Minimum fields: client-IP:gateway:netmask:hostname:interface:method (5 colons)
|
|
# Extended: adds :dns1 and/or :dns2 (up to 7 colons)
|
|
temp_ip=${HCN_IP}
|
|
colons=0
|
|
while [ "${temp_ip#*:}" != "$temp_ip" ]; do
|
|
colons=$((colons + 1))
|
|
temp_ip=${temp_ip#*:}
|
|
done
|
|
|
|
if [ "$colons" -ge 5 ]; then
|
|
# Already has interface field, just need to replace it with bond name
|
|
# Extract fields: we need to replace the 6th field (interface) with BONDNAME
|
|
# Preserve any DNS fields that may follow
|
|
# shellcheck disable=SC2034
|
|
IFS=':' read -r f1 f2 f3 f4 f5 f6 f7 f8 f9 <<EOF
|
|
$HCN_IP
|
|
EOF
|
|
# Reconstruct with bond name as interface (f6 position)
|
|
# Note: f7 and beyond might be method:dns1:dns2 or just method
|
|
if [ -n "$f9" ]; then
|
|
# Has dns2 field
|
|
info "parse-hcn: applying static IP config with DNS1 and DNS2 to $BONDNAME"
|
|
NEW_ARGS="$NEW_ARGS ip=$f1:$f2:$f3:$f4:$f5:$BONDNAME:$f7:$f8:$f9"
|
|
elif [ -n "$f8" ]; then
|
|
# Has dns1 field
|
|
info "parse-hcn: applying static IP config with DNS1 to $BONDNAME"
|
|
NEW_ARGS="$NEW_ARGS ip=$f1:$f2:$f3:$f4:$f5:$BONDNAME:$f7:$f8"
|
|
elif [ -n "$f7" ]; then
|
|
# Standard format with method
|
|
info "parse-hcn: applying static IP config to $BONDNAME"
|
|
NEW_ARGS="$NEW_ARGS ip=$f1:$f2:$f3:$f4:$f5:$BONDNAME:$f7"
|
|
else
|
|
# Fallback - just interface name
|
|
NEW_ARGS="$NEW_ARGS ip=$f1:$f2:$f3:$f4:$f5:$BONDNAME"
|
|
fi
|
|
elif [ "$colons" -lt 5 ]; then
|
|
# Pad with colons to reach standard ip= format (legacy behavior)
|
|
case $((5 - colons)) in
|
|
1) suffix=":" ;;
|
|
2) suffix="::" ;;
|
|
3) suffix=":::" ;;
|
|
4) suffix="::::" ;;
|
|
5) suffix=":::::" ;;
|
|
*) suffix="" ;;
|
|
esac
|
|
info "parse-hcn: applying static IP config to $BONDNAME (padded)"
|
|
NEW_ARGS="$NEW_ARGS ip=$HCN_IP${suffix}$BONDNAME:none"
|
|
fi
|
|
;;
|
|
esac
|
|
fi
|
|
done
|
|
|
|
# Process rd.hcn.route= - replace slave names/MACs with bond name
|
|
for HCN_ROUTE in $(getargs rd.hcn.route); do
|
|
# Lowercased copy for case-insensitive MAC matching (see rd.hcn.ip above).
|
|
HCN_ROUTE_LC=$(echo "$HCN_ROUTE" | tr 'A-Z' 'a-z')
|
|
matched=0
|
|
for slave in $SLAVE_NAMES $SLAVE_MACS; do
|
|
slave_dash=$(str_replace "$slave" ":" "-")
|
|
hcn_cmp=$HCN_ROUTE
|
|
case "$slave" in *:*) hcn_cmp=$HCN_ROUTE_LC ;; esac
|
|
if strstr ":$hcn_cmp:" ":$slave:" || strstr ":$hcn_cmp:" ":$slave_dash:"; then
|
|
matched=1
|
|
current_hcn_route=$(echo "$hcn_cmp" | sed -E "s#^($slave|$slave_dash)([: ]|$)#$BONDNAME\2#; s#([: ])($slave|$slave_dash)([: ]|$)#\1$BONDNAME\3#g")
|
|
NEW_ARGS="$NEW_ARGS rd.route=$current_hcn_route"
|
|
break
|
|
fi
|
|
done
|
|
|
|
# Check if this route is targeted at another bond's slave
|
|
matched_other=0
|
|
for slave in $OTHER_SLAVE_NAMES $OTHER_SLAVE_MACS; do
|
|
slave_dash=$(str_replace "$slave" ":" "-")
|
|
hcn_cmp=$HCN_ROUTE
|
|
case "$slave" in *:*) hcn_cmp=$HCN_ROUTE_LC ;; esac
|
|
if strstr ":$hcn_cmp:" ":$slave:" || strstr ":$hcn_cmp:" ":$slave_dash:"; then
|
|
matched_other=1
|
|
break
|
|
fi
|
|
done
|
|
|
|
# No slave matched and no bond name present - apply to first bond
|
|
has_bond_route=0
|
|
case "$HCN_ROUTE" in
|
|
*:bond[0-9]*) has_bond_route=1 ;;
|
|
esac
|
|
if [ $matched -eq 0 ] && [ $matched_other -eq 0 ] && [ $has_bond_route -eq 0 ] && [ "$BONDNAME" = "$FIRST_BOND" ]; then
|
|
temp_route=${HCN_ROUTE}
|
|
colons=0
|
|
while [ "${temp_route#*:}" != "$temp_route" ]; do
|
|
colons=$((colons + 1))
|
|
temp_route=${temp_route#*:}
|
|
done
|
|
if [ "$colons" -le 1 ]; then
|
|
info "parse-hcn: applying route to $BONDNAME"
|
|
NEW_ARGS="$NEW_ARGS rd.route=$HCN_ROUTE$([ "$colons" -eq 0 ] && echo "::" || echo ":")$BONDNAME"
|
|
fi
|
|
fi
|
|
done
|
|
done
|
|
|
|
carry_over_cmdline
|
|
|
|
# Write new configuration and update NetworkManager
|
|
if [ -n "$NEW_ARGS" ]; then
|
|
# Create runtime directories to store HCN connections and the generator
|
|
# configuration files
|
|
mkdir -p "$HCN_RUNTIME_CONN_DIR" "$HCN_RUNTIME_CONF_DIR"
|
|
|
|
# Write new cmdline options
|
|
info "parse-hcn: new cmdline arguments: $NEW_ARGS"
|
|
echo "$NEW_ARGS" > "$HCN_RUNTIME_DIR"/cmdline
|
|
|
|
# Find and execute nm-initrd-generator
|
|
generator_found=0
|
|
for gen in /usr/lib/nm-initrd-generator /usr/libexec/nm-initrd-generator; do
|
|
[ -x "$gen" ] || continue
|
|
generator_found=1
|
|
info "parse-hcn: calling $gen"
|
|
rm -f "$HCN_RUNTIME_CONN_DIR"/*
|
|
# --run-config-dir points to a scratch directory on purpose. The generator
|
|
# always writes 15-carrier-timeout.conf, and with the default directory
|
|
# this run would overwrite the one NetworkManager generated from the real
|
|
# command line, resetting a user supplied rd.net.timeout.carrier.
|
|
# shellcheck disable=SC2086
|
|
if "$gen" -c "$HCN_RUNTIME_CONN_DIR" -r "$HCN_RUNTIME_CONF_DIR" -- $NEW_ARGS; then
|
|
if [ "$(ls -A "$HCN_RUNTIME_CONN_DIR")" ]; then
|
|
fixup_nm_connections
|
|
# Persist these new HCN connections to /etc
|
|
mkdir -p "$NM_CONF_CONN_DIR"
|
|
cp -rf "$HCN_RUNTIME_CONN_DIR"/* "$NM_CONF_CONN_DIR"
|
|
mkdir -p "$NM_RUNTIME_DIR"/initrd
|
|
: > "$NM_RUNTIME_DIR"/initrd/neednet
|
|
echo '[ -f /tmp/nm.done ]' > "$hookdir"/initqueue/finished/nm.sh
|
|
else
|
|
warn "parse-hcn: nm-initrd-generator did not generate any connections"
|
|
fi
|
|
else
|
|
warn "parse-hcn: nm-initrd-generator failed"
|
|
fi
|
|
break
|
|
done
|
|
|
|
if [ $generator_found -eq 0 ]; then
|
|
warn "parse-hcn: nm-initrd-generator not found"
|
|
fi
|
|
fi
|