Skip to content

Commit

Permalink
Add static network setup: (tinkerbell#251)
Browse files Browse the repository at this point in the history
## Description

<!--- Please describe what this PR is going to change -->

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=<mac-address>:<vlan-id>:<ip-address>:<netmask>:<gateway>:<hostname>:<dns>:<search-domains>:<ntp>`

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 by 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`.

## Why is this needed

<!--- Link to issue you have raised -->

Fixes: #

## How Has This Been Tested?
<!--- Please describe in detail how you tested your changes. -->
<!--- Include details of your testing environment, and the tests you ran
to -->
<!--- see how your change affects other areas of the code, etc. -->


## How are existing users impacted? What migration steps/scripts do we
need?

<!--- Fixes a bug, unblocks installation, removes a component of the
stack etc -->
<!--- Requires a DB migration script, etc. -->


## Checklist:

I have:

- [ ] updated the documentation and/or roadmap (if required)
- [ ] added unit or e2e tests
- [ ] provided instructions on how to upgrade
  • Loading branch information
jacobweinstock authored Nov 23, 2024
2 parents b5560c5 + eba4d71 commit 68fdd4d
Show file tree
Hide file tree
Showing 8 changed files with 252 additions and 69 deletions.
1 change: 0 additions & 1 deletion bash/hook-lk-containers.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 2 additions & 3 deletions bash/linuxkit.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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=""
Expand Down
9 changes: 8 additions & 1 deletion files/dhcp.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
50 changes: 50 additions & 0 deletions files/setup-dns.sh
Original file line number Diff line number Diff line change
@@ -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
138 changes: 138 additions & 0 deletions files/static-network.sh
Original file line number Diff line number Diff line change
@@ -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=<mac-address>:<vlan-id>:<ip-address>:<netmask>:<gateway>:<hostname>:<dns>:<search-domains>:<ntp>
# 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 <<EOF
${ipam}
EOF

# Check for required fields
if [ -z "$mac" ] || [ -z "$ip" ] || [ -z "$netmask" ] || [ -z "$dns" ]; then
echo "Error: MAC address, IP address, netmask, and DNS are required."
echo "$ipam"
exit 1
fi

# convert Mac address to colon separated format
mac=$(convert_hyphen_to_colon "$mac")

# convert , (comma) separated values to space separated values
dns=$(echo "$dns" | tr ',' ' ')
search_domains=$(echo "$search_domains" | tr ',' ' ')
ntp=$(echo "$ntp" | tr ',' ' ')

# Get interface name
interface=$(get_interface_name "$mac")
if [ -z "$interface" ]; then
echo "Error: No interface found with MAC address $mac"
exit 1
fi

# Start writing to the interfaces file
{
echo "# Static Network configuration for $interface"
echo ""
echo "auto $interface"

if [ -n "$vlan_id" ]; then
echo "iface $interface inet manual"
echo ""
echo "auto $interface.$vlan_id"
echo "iface $interface.$vlan_id inet static"
else
echo "iface $interface inet static"
fi

echo " address $ip"
echo " netmask $netmask"

[ -n "$gateway" ] && echo " gateway $gateway"
[ -n "$hostname" ] && echo " hostname $hostname"

if [ -n "$dns" ]; then
echo " dns-nameserver $dns"
fi

if [ -n "$search_domains" ]; then
echo " dns-search $search_domains"
fi

if [ -n "$ntp" ]; then
echo " ntp-servers $ntp"
fi

} > "$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"
73 changes: 42 additions & 31 deletions files/vlan.sh
Original file line number Diff line number Diff line change
@@ -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
Expand Down
23 changes: 0 additions & 23 deletions images/hook-ip/Dockerfile

This file was deleted.

Loading

0 comments on commit 68fdd4d

Please sign in to comment.