#!/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. Generate NetworkManager connections to a temporary directory via nm-initrd-generator # 6. Fix up generated connections to use correct bond masters and naming # 7. Copy connections to /etc/NetworkManager/system-connections for persistence # # Usage: # rd.hcn=1 - Create bond connections only (no IP configuration) # rd.hcn.ip= - Create bonds with HCN-specific IP configuration # rd.hcn.route= - 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 } # Function to discover HCN mapping for a device-tree node get_dev_hcn() { local dev=$1 local hcnid devname mode mac ofpath # Wait up to 3 minutes for device to appear after migration (12 * 15s) local wait=12 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. while [ $wait -gt 0 ]; do if devname=$(ofpathname -l "$ofpath" 2>/dev/null) && [ -e "/sys/class/net/$devname" ]; then info "parse-hcn: device $devname ready for $ofpath" mac=$(get_mac "$dev") break fi info "parse-hcn: waiting for device for $ofpath (retry $wait)" sleep 15 wait=$((wait - 1)) done if [ -z "$devname" ]; then warn "parse-hcn: could not resolve device name for $dev" return 1 fi # Output the bond mapping: bondname devname mac mode echo "bond$hcnid $devname ${mac:-none} ${mode:-none}" return 0 } # Get a value from a keyfile (INI format) # Usage: gkeyfile_get
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
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
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 <:{dhcp|on|any|dhcp6|auto6|link6}[:[]] # 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 - count colons to determine format # Standard format: rd.hcn.ip=:[]:::::{none|off|dhcp|on|any|dhcp6|auto6|ibft}[:[][:]] # 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 < "$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"/* # shellcheck disable=SC2086 if "$gen" -c "$HCN_RUNTIME_CONN_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