From a5766439d16fecd89d9e55cdc5c6790f92bb2dcf Mon Sep 17 00:00:00 2001 From: Jacob Weinstock Date: Fri, 22 Nov 2024 17:54:40 -0700 Subject: [PATCH 1/2] Add static network script: For environments where DHCP is not available, I've added a script that will statically configure network interfaces. The IPAM info must be passed in via kernel cmdline parameters and be in the appropriate format. ipam=:::::::: This is probably only useful for the HookOS ISO. For the Tinkerbell stack, Smee handles patching the ISO at runtime to include this `ipam=` parameter. To facilitate this static ip configuration, scripts were placed into the host filesystem at /etc/init.d. Files in this location are run at startup my the init system. This makes it possible to just add the scripts as files in the linuxkit yaml file. The vlan.sh script was moved to an init.d script because it needs to run before the static-network script. The vlan.sh script was updated to use posix /bin/sh instead of bash because the host only has /bin/sh. Signed-off-by: Jacob Weinstock --- bash/hook-lk-containers.sh | 1 - bash/linuxkit.sh | 5 +- files/dhcp.sh | 9 +- files/setup-dns.sh | 50 ++++++++++ files/static-network.sh | 138 ++++++++++++++++++++++++++ files/vlan.sh | 73 ++++++++------ linuxkit-templates/hook.template.yaml | 22 ++-- 7 files changed, 252 insertions(+), 46 deletions(-) create mode 100755 files/setup-dns.sh create mode 100755 files/static-network.sh diff --git a/bash/hook-lk-containers.sh b/bash/hook-lk-containers.sh index 9c5c8b15..33280727 100644 --- a/bash/hook-lk-containers.sh +++ b/bash/hook-lk-containers.sh @@ -7,7 +7,6 @@ function build_all_hook_linuxkit_containers() { # when adding new container builds here you'll also want to add them to the # `linuxkit_build` function in the linuxkit.sh file. # # NOTE: linuxkit containers must be in the images/ directory - build_hook_linuxkit_container hook-ip HOOK_CONTAINER_IP_IMAGE build_hook_linuxkit_container hook-bootkit HOOK_CONTAINER_BOOTKIT_IMAGE build_hook_linuxkit_container hook-docker HOOK_CONTAINER_DOCKER_IMAGE build_hook_linuxkit_container hook-mdev HOOK_CONTAINER_MDEV_IMAGE diff --git a/bash/linuxkit.sh b/bash/linuxkit.sh index 1dd051f3..4e8fa19a 100644 --- a/bash/linuxkit.sh +++ b/bash/linuxkit.sh @@ -50,7 +50,7 @@ function linuxkit_build() { fi # Build the containers in this repo used in the LinuxKit YAML; - build_all_hook_linuxkit_containers # sets HOOK_CONTAINER_IP_IMAGE, HOOK_CONTAINER_BOOTKIT_IMAGE, HOOK_CONTAINER_DOCKER_IMAGE, HOOK_CONTAINER_MDEV_IMAGE, HOOK_CONTAINER_CONTAINERD_IMAGE + build_all_hook_linuxkit_containers # sets HOOK_CONTAINER_BOOTKIT_IMAGE, HOOK_CONTAINER_DOCKER_IMAGE, HOOK_CONTAINER_MDEV_IMAGE, HOOK_CONTAINER_CONTAINERD_IMAGE # Template the linuxkit configuration file. # - You'd think linuxkit would take --build-args or something by now, but no. @@ -64,14 +64,13 @@ function linuxkit_build() { # shellcheck disable=SC2016 # I'm using single quotes to avoid shell expansion, envsubst wants the dollar signs. cat "linuxkit-templates/${kernel_info['TEMPLATE']}.template.yaml" | HOOK_KERNEL_IMAGE="${kernel_oci_image}" HOOK_KERNEL_ID="${inventory_id}" HOOK_KERNEL_VERSION="${kernel_oci_version}" \ - HOOK_CONTAINER_IP_IMAGE="${HOOK_CONTAINER_IP_IMAGE}" \ HOOK_CONTAINER_BOOTKIT_IMAGE="${HOOK_CONTAINER_BOOTKIT_IMAGE}" \ HOOK_CONTAINER_DOCKER_IMAGE="${HOOK_CONTAINER_DOCKER_IMAGE}" \ HOOK_CONTAINER_MDEV_IMAGE="${HOOK_CONTAINER_MDEV_IMAGE}" \ HOOK_CONTAINER_CONTAINERD_IMAGE="${HOOK_CONTAINER_CONTAINERD_IMAGE}" \ HOOK_CONTAINER_RUNC_IMAGE="${HOOK_CONTAINER_RUNC_IMAGE}" \ HOOK_CONTAINER_EMBEDDED_IMAGE="${HOOK_CONTAINER_EMBEDDED_IMAGE}" \ - envsubst '$HOOK_VERSION $HOOK_KERNEL_IMAGE $HOOK_KERNEL_ID $HOOK_KERNEL_VERSION $HOOK_CONTAINER_IP_IMAGE $HOOK_CONTAINER_BOOTKIT_IMAGE $HOOK_CONTAINER_DOCKER_IMAGE $HOOK_CONTAINER_MDEV_IMAGE $HOOK_CONTAINER_CONTAINERD_IMAGE $HOOK_CONTAINER_RUNC_IMAGE $HOOK_CONTAINER_EMBEDDED_IMAGE' \ + envsubst '$HOOK_VERSION $HOOK_KERNEL_IMAGE $HOOK_KERNEL_ID $HOOK_KERNEL_VERSION $HOOK_CONTAINER_BOOTKIT_IMAGE $HOOK_CONTAINER_DOCKER_IMAGE $HOOK_CONTAINER_MDEV_IMAGE $HOOK_CONTAINER_CONTAINERD_IMAGE $HOOK_CONTAINER_RUNC_IMAGE $HOOK_CONTAINER_EMBEDDED_IMAGE' \ > "hook.${inventory_id}.yaml" declare -g linuxkit_bin="" diff --git a/files/dhcp.sh b/files/dhcp.sh index c10ba3dc..4359572a 100755 --- a/files/dhcp.sh +++ b/files/dhcp.sh @@ -18,7 +18,9 @@ run_dhcp_client() { if [ "$one_shot" = "true" ]; then # always return true for the one shot dhcp call so it doesn't block Hook from starting up. - /sbin/dhcpcd --nobackground -f /dhcpcd.conf --allowinterfaces "${al}" -1 || true + # the --nobackground is not used here because when it is used, dhcpcd doesn't honor the --timeout option + # and waits indefinitely for a response. For one shot, we want to timeout after the 30 second default. + /sbin/dhcpcd -f /dhcpcd.conf --allowinterfaces "${al}" -1 || true # use busybox's ntpd to set the time after getting an IP address; don't fail echo 'sleep 1 second before calling ntpd' && sleep 1 /usr/sbin/ntpd -n -q -dd -p pool.ntp.org || true @@ -28,6 +30,11 @@ run_dhcp_client() { } +if [ -f /run/network/interfaces ]; then + echo "the /run/network/interfaces file exists, so static IP's are in use. we will not be running the dhcp client." + exit 0 +fi + # we always return true so that a failure here doesn't block the next container service from starting. Ideally, we always # want the getty service to start so we can debug failures. run_dhcp_client "$1" || true diff --git a/files/setup-dns.sh b/files/setup-dns.sh new file mode 100755 index 00000000..c6ffca5e --- /dev/null +++ b/files/setup-dns.sh @@ -0,0 +1,50 @@ +#!/bin/sh + +# This script is intended to be run on the HookOS/Linuxkit host so it must use /bin/sh. +# No other shells are available on the host. + +# modified from alpine setup-dns +# apk add alpine-conf + +exec 3>&1 4>&2 +trap 'exec 2>&4 1>&3' 0 1 2 3 +exec 1>/var/log/setup-dns.log 2>&1 + +while getopts "d:n:h" opt; do + case $opt in + d) DOMAINNAME="$OPTARG";; + n) NAMESERVERS="$OPTARG";; + esac +done +shift $(($OPTIND - 1)) + + +conf="${ROOT}resolv.conf" + +if [ -f "$conf" ] ; then + domain=$(awk '/^domain/ {print $2}' $conf) + dns=$(awk '/^nameserver/ {printf "%s ",$2}' $conf) +elif fqdn="$(get_fqdn)" && [ -n "$fqdn" ]; then + domain="$fqdn" +fi + +if [ -n "$DOMAINNAME" ]; then + domain="$DOMAINNAME" +fi + +if [ -n "$NAMESERVERS" ] || [ $# -gt 0 ];then + dns="$NAMESERVERS" +fi + +if [ -n "$domain" ]; then + mkdir -p "${conf%/*}" + echo "search $domain" > $conf +fi + +if [ -n "$dns" ] || [ $# -gt 0 ] && [ -f "$conf" ]; then + sed -i -e '/^nameserver/d' $conf +fi +for i in $dns $@; do + mkdir -p "${conf%/*}" + echo "nameserver $i" >> $conf +done diff --git a/files/static-network.sh b/files/static-network.sh new file mode 100755 index 00000000..e40b982f --- /dev/null +++ b/files/static-network.sh @@ -0,0 +1,138 @@ +#!/bin/sh + +# This script is intended to be run on the HookOS/Linuxkit host so it must use /bin/sh. +# No other shells are available on the host. + +# this script will statically configure a single network interface based on the ipam= parameter +# passed in the kernel command line. The ipam parameter is a colon separated string with the following fields: +# ipam=:::::::: +# Example: ipam=de-ad-be-ef-fe-ed::192.168.2.193:255.255.255.0:192.168.2.1:myserver:1.1.1.1,8.8.8.8::132.163.97.1,132.163.96.1 +# the mac address format requires it to be hyphen separated. + +exec 3>&1 4>&2 +trap 'exec 2>&4 1>&3' 0 1 2 3 +exec 1>/var/log/network_config.log 2>&1 + +set -xeuo pipefail + +# Define the location of the interfaces file +INTERFACES_FILE="/var/run/network/interfaces" + +parse_ipam_from_cmdline() { + local cmdline + local ipam_value + + # Read the contents of /proc/cmdline + cmdline=$(cat /proc/cmdline) + + # Use grep to find the ipam= parameter and awk to extract its value + ipam_value=$(echo "$cmdline" | grep -o 'ipam=[^ ]*' | awk -F= '{print $2}') + + # Check if ipam= parameter was found + if [ -n "$ipam_value" ]; then + echo "$ipam_value" + return 0 + else + echo "ipam= parameter not found in /proc/cmdline" >&2 + return 1 + fi +} + +# Function to get interface name from MAC address +# TODO(jacobweinstock): if a vlan id is provided we should match for the vlan interface +get_interface_name() { + local mac=$1 + for interface in /sys/class/net/*; do + if [ -f "$interface/address" ]; then + if [ "$(cat "$interface/address")" == "$mac" ]; then + echo "$(basename "$interface")" + return 0 + fi + fi + done + return 1 +} + +convert_hyphen_to_colon() { + echo "$1" | tr '-' ':' +} + +ipam=$(parse_ipam_from_cmdline) +if [ $? -ne 0 ]; then + echo "Failed to get IPAM value, not statically configuring network" + cat /proc/cmdline + exit 0 +fi +echo "IPAM value: $ipam" + +mkdir -p $(dirname "$INTERFACES_FILE") + +# Parse the IPAM string +IFS=':' read -r mac vlan_id ip netmask gateway hostname dns search_domains ntp < "$INTERFACES_FILE" + +echo "Network configuration has been written to $INTERFACES_FILE" + +# Run ifup on the interface +ifup -v -a -i "$INTERFACES_FILE" + +# setup DNS +ROOT=/run/resolvconf/ setup-dns -d "$search_domains" "$dns" diff --git a/files/vlan.sh b/files/vlan.sh index 449ccdd7..d04b76b3 100755 --- a/files/vlan.sh +++ b/files/vlan.sh @@ -1,63 +1,74 @@ -#!/bin/bash +#!/bin/sh + +# This script is intended to be run on the HookOS/Linuxkit host so it must use /bin/sh. +# No other shells are available on the host. + +exec 3>&1 4>&2 +trap 'exec 2>&4 1>&3' 0 1 2 3 +exec 1>/var/log/vlan.log 2>&1 set -e # exit on error # This script will set up VLAN interfaces if `vlan_id=xxxx` in `/proc/cmdline` has a value. # It will use the MAC address specified in `hw_addr=` to find the interface to add the VLAN to. -function parse_with_regex_power() { - declare stdin_data cmdline_rest - stdin_data="$(cat)" # read stdin - declare search_argument="${1}" - declare normal_matcher="([a-zA-Z0-9/\\@#\$%^&\!*\(\)'\"=:,._-]+)" - declare quoted_matcher="\"([a-zA-Z0-9/\\@#\$%^&\!*\(\)',=: ._-]+)\"" - [ $# -gt 1 ] && normal_matcher="$2" && quoted_matcher="$2" - cmdline_rest="$(printf '%s' "$stdin_data" | sed -rn "s/.* ?${search_argument}=${normal_matcher} ?(.*)+?/\1/p")" - if echo "$cmdline_rest" | grep -Eq '^"'; then - cmdline_rest="$(printf "%s\n" "$stdin_data" | sed -rn "s/.* ?${search_argument}=${quoted_matcher} ?(.*)+?/\1/p")" - fi - printf "%s\n" "$cmdline_rest" -} +parse_from_cmdline() { + local key="${1}" + local cmdline + local ipam_value -function parse_kernel_cmdline_for() { - declare result - # shellcheck disable=SC2002 - result=$(cat /proc/cmdline | parse_with_regex_power "$@") - if [ -z "${result}" ]; then - return 1 - else - printf "%s" "$result" - fi + # Read the contents of /proc/cmdline + cmdline=$(cat /proc/cmdline) + + # Use grep to find the ipam= parameter and awk to extract its value + value=$(echo "$cmdline" | grep -o "${key}=[^ ]*" | awk -F= '{print $2}') + + # Check if parameter was found + if [ -n "$value" ]; then + echo "$value" + return 0 + else + echo "${key}= parameter not found in /proc/cmdline" >&2 + return 1 + fi } -function kernel_cmdline_exists() { - parse_kernel_cmdline_for "$@" > /dev/null +get_interface_name() { + local mac=$1 + for interface in /sys/class/net/*; do + if [ -f "$interface/address" ]; then + if [ "$(cat "$interface/address")" == "$mac" ]; then + echo "$(basename "$interface")" + return 0 + fi + fi + done + return 1 } function add_vlan_interface() { # check if vlan_id are set in the kernel commandline, otherwise return. - if ! kernel_cmdline_exists vlan_id; then + if ! parse_from_cmdline vlan_id; then echo "No vlan_id=xxxx set in kernel commandline; no VLAN handling." >&2 return fi # check if hw_addr are set in the kernel commandline, otherwise return. - if ! kernel_cmdline_exists hw_addr; then + if ! parse_from_cmdline hw_addr; then echo "No hw_addr=xx:xx:xx:xx:xx:xx set in kernel commandline." >&2 fi echo "Starting VLAN handling, parsing..." >&2 - declare vlan_id hw_addr - vlan_id="$(parse_kernel_cmdline_for vlan_id)" - hw_addr="$(parse_kernel_cmdline_for hw_addr)" + vlan_id="$(parse_from_cmdline vlan_id)" + hw_addr="$(parse_from_cmdline hw_addr)" echo "VLAN handling - vlan_id: '${vlan_id}', hw_addr: '${hw_addr}'" >&2 if [ -n "$vlan_id" ]; then if [ -n "$hw_addr" ]; then echo "VLAN handling - vlan_id: '${vlan_id}', hw_addr: '${hw_addr}', searching for interface..." >&2 - ifname="$(ip -br link | awk '$3 ~ /'"${hw_addr}"'/ {print $1}')" + ifname="$(get_interface_name ${hw_addr})" echo "VLAN handling - vlan_id: '${vlan_id}', hw_addr: '${hw_addr}', found interface: '${ifname}'" >&2 else echo "VLAN handling - vlan_id: '${vlan_id}', hw_addr: '${hw_addr}', no hw_addr found in kernel commandline; default ifname to eth0." >&2 diff --git a/linuxkit-templates/hook.template.yaml b/linuxkit-templates/hook.template.yaml index 98cfb192..42d8c0b8 100644 --- a/linuxkit-templates/hook.template.yaml +++ b/linuxkit-templates/hook.template.yaml @@ -3,7 +3,6 @@ # - HOOK_KERNEL_IMAGE: ${HOOK_KERNEL_IMAGE} # - HOOK_KERNEL_ID: ${HOOK_KERNEL_ID} # - HOOK_KERNEL_VERSION: ${HOOK_KERNEL_VERSION} -# - HOOK_CONTAINER_IP_IMAGE: ${HOOK_CONTAINER_IP_IMAGE} # - HOOK_CONTAINER_BOOTKIT_IMAGE: ${HOOK_CONTAINER_BOOTKIT_IMAGE} # - HOOK_CONTAINER_DOCKER_IMAGE: ${HOOK_CONTAINER_DOCKER_IMAGE} # - HOOK_CONTAINER_MDEV_IMAGE: ${HOOK_CONTAINER_MDEV_IMAGE} @@ -42,14 +41,6 @@ onboot: image: linuxkit/modprobe:v1.0.0 command: [ "modprobe", "cdc_ncm" ] # for usb ethernet dongles - - name: vlan - image: "${HOOK_CONTAINER_IP_IMAGE}" - capabilities: - - all - binds.add: - - /etc/ip/vlan.sh:/etc/ip/vlan.sh - command: [ "/etc/ip/vlan.sh" ] - - name: dhcpcd-once image: linuxkit/dhcpcd:v1.0.0 command: [ "/etc/ip/dhcp.sh", "true" ] # 2nd paramter is one-shot true/false: true for onboot, false for services @@ -275,10 +266,21 @@ files: ANSI_COLOR="1;34" HOME_URL="https://github.com/tinkerbell/hook" - - path: etc/ip/vlan.sh + # Putting scripts in /etc/init.d/ allows them to be run at boot time + - path: etc/init.d/002-vlan.sh source: "files/vlan.sh" mode: "0777" + # Putting scripts in /etc/init.d/ allows them to be run at boot time + - path: etc/init.d/003-static-network.sh + source: "files/static-network.sh" + mode: "0777" + + # This makes the script available in the host PATH + - path: sbin/setup-dns + source: "files/setup-dns.sh" + mode: "0777" + - path: etc/ip/dhcp.sh source: "files/dhcp.sh" mode: "0777" From eba4d71081224912cc93fc7c149b798b5eb29815 Mon Sep 17 00:00:00 2001 From: Jacob Weinstock Date: Sat, 23 Nov 2024 09:05:35 -0700 Subject: [PATCH 2/2] Remove unused IP image: With the move of the vlan script to an init.d script the image build for IP is not needed. Signed-off-by: Jacob Weinstock --- images/hook-ip/Dockerfile | 23 ----------------------- linuxkit-templates/hook.template.yaml | 6 +++--- 2 files changed, 3 insertions(+), 26 deletions(-) delete mode 100644 images/hook-ip/Dockerfile diff --git a/images/hook-ip/Dockerfile b/images/hook-ip/Dockerfile deleted file mode 100644 index 17bb8d29..00000000 --- a/images/hook-ip/Dockerfile +++ /dev/null @@ -1,23 +0,0 @@ -FROM linuxkit/alpine:146f540f25cd92ec8ff0c5b0c98342a9a95e479e AS mirror -RUN mkdir -p /out/etc/apk && cp -r /etc/apk/* /out/etc/apk/ -RUN apk add curl -RUN apk add --no-cache --initdb -p /out \ - alpine-baselayout \ - bash \ - busybox \ - iproute2 \ - iptables \ - ebtables \ - ipvsadm \ - bridge-utils \ - musl \ - sed - -# Remove apk residuals -RUN rm -rf /out/etc/apk /out/lib/apk /out/var/cache - -FROM scratch -ENTRYPOINT [] -CMD [] -WORKDIR / -COPY --from=mirror /out/ / diff --git a/linuxkit-templates/hook.template.yaml b/linuxkit-templates/hook.template.yaml index 42d8c0b8..8eb33740 100644 --- a/linuxkit-templates/hook.template.yaml +++ b/linuxkit-templates/hook.template.yaml @@ -266,17 +266,17 @@ files: ANSI_COLOR="1;34" HOME_URL="https://github.com/tinkerbell/hook" - # Putting scripts in /etc/init.d/ allows them to be run at boot time + # Putting scripts in /etc/init.d/ allows them to be run at boot time by the init system - path: etc/init.d/002-vlan.sh source: "files/vlan.sh" mode: "0777" - # Putting scripts in /etc/init.d/ allows them to be run at boot time + # Putting scripts in /etc/init.d/ allows them to be run at boot time by the init system - path: etc/init.d/003-static-network.sh source: "files/static-network.sh" mode: "0777" - # This makes the script available in the host PATH + # This makes the script available in the host $PATH - path: sbin/setup-dns source: "files/setup-dns.sh" mode: "0777"