Skip to content

Commit 68fdd4d

Browse files
Add static network setup: (#251)
## 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
2 parents b5560c5 + eba4d71 commit 68fdd4d

File tree

8 files changed

+252
-69
lines changed

8 files changed

+252
-69
lines changed

bash/hook-lk-containers.sh

-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ function build_all_hook_linuxkit_containers() {
77
# when adding new container builds here you'll also want to add them to the
88
# `linuxkit_build` function in the linuxkit.sh file.
99
# # NOTE: linuxkit containers must be in the images/ directory
10-
build_hook_linuxkit_container hook-ip HOOK_CONTAINER_IP_IMAGE
1110
build_hook_linuxkit_container hook-bootkit HOOK_CONTAINER_BOOTKIT_IMAGE
1211
build_hook_linuxkit_container hook-docker HOOK_CONTAINER_DOCKER_IMAGE
1312
build_hook_linuxkit_container hook-mdev HOOK_CONTAINER_MDEV_IMAGE

bash/linuxkit.sh

+2-3
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ function linuxkit_build() {
5050
fi
5151

5252
# Build the containers in this repo used in the LinuxKit YAML;
53-
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
53+
build_all_hook_linuxkit_containers # sets HOOK_CONTAINER_BOOTKIT_IMAGE, HOOK_CONTAINER_DOCKER_IMAGE, HOOK_CONTAINER_MDEV_IMAGE, HOOK_CONTAINER_CONTAINERD_IMAGE
5454

5555
# Template the linuxkit configuration file.
5656
# - You'd think linuxkit would take --build-args or something by now, but no.
@@ -64,14 +64,13 @@ function linuxkit_build() {
6464
# shellcheck disable=SC2016 # I'm using single quotes to avoid shell expansion, envsubst wants the dollar signs.
6565
cat "linuxkit-templates/${kernel_info['TEMPLATE']}.template.yaml" |
6666
HOOK_KERNEL_IMAGE="${kernel_oci_image}" HOOK_KERNEL_ID="${inventory_id}" HOOK_KERNEL_VERSION="${kernel_oci_version}" \
67-
HOOK_CONTAINER_IP_IMAGE="${HOOK_CONTAINER_IP_IMAGE}" \
6867
HOOK_CONTAINER_BOOTKIT_IMAGE="${HOOK_CONTAINER_BOOTKIT_IMAGE}" \
6968
HOOK_CONTAINER_DOCKER_IMAGE="${HOOK_CONTAINER_DOCKER_IMAGE}" \
7069
HOOK_CONTAINER_MDEV_IMAGE="${HOOK_CONTAINER_MDEV_IMAGE}" \
7170
HOOK_CONTAINER_CONTAINERD_IMAGE="${HOOK_CONTAINER_CONTAINERD_IMAGE}" \
7271
HOOK_CONTAINER_RUNC_IMAGE="${HOOK_CONTAINER_RUNC_IMAGE}" \
7372
HOOK_CONTAINER_EMBEDDED_IMAGE="${HOOK_CONTAINER_EMBEDDED_IMAGE}" \
74-
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' \
73+
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' \
7574
> "hook.${inventory_id}.yaml"
7675

7776
declare -g linuxkit_bin=""

files/dhcp.sh

+8-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ run_dhcp_client() {
1818

1919
if [ "$one_shot" = "true" ]; then
2020
# always return true for the one shot dhcp call so it doesn't block Hook from starting up.
21-
/sbin/dhcpcd --nobackground -f /dhcpcd.conf --allowinterfaces "${al}" -1 || true
21+
# the --nobackground is not used here because when it is used, dhcpcd doesn't honor the --timeout option
22+
# and waits indefinitely for a response. For one shot, we want to timeout after the 30 second default.
23+
/sbin/dhcpcd -f /dhcpcd.conf --allowinterfaces "${al}" -1 || true
2224
# use busybox's ntpd to set the time after getting an IP address; don't fail
2325
echo 'sleep 1 second before calling ntpd' && sleep 1
2426
/usr/sbin/ntpd -n -q -dd -p pool.ntp.org || true
@@ -28,6 +30,11 @@ run_dhcp_client() {
2830

2931
}
3032

33+
if [ -f /run/network/interfaces ]; then
34+
echo "the /run/network/interfaces file exists, so static IP's are in use. we will not be running the dhcp client."
35+
exit 0
36+
fi
37+
3138
# we always return true so that a failure here doesn't block the next container service from starting. Ideally, we always
3239
# want the getty service to start so we can debug failures.
3340
run_dhcp_client "$1" || true

files/setup-dns.sh

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
#!/bin/sh
2+
3+
# This script is intended to be run on the HookOS/Linuxkit host so it must use /bin/sh.
4+
# No other shells are available on the host.
5+
6+
# modified from alpine setup-dns
7+
# apk add alpine-conf
8+
9+
exec 3>&1 4>&2
10+
trap 'exec 2>&4 1>&3' 0 1 2 3
11+
exec 1>/var/log/setup-dns.log 2>&1
12+
13+
while getopts "d:n:h" opt; do
14+
case $opt in
15+
d) DOMAINNAME="$OPTARG";;
16+
n) NAMESERVERS="$OPTARG";;
17+
esac
18+
done
19+
shift $(($OPTIND - 1))
20+
21+
22+
conf="${ROOT}resolv.conf"
23+
24+
if [ -f "$conf" ] ; then
25+
domain=$(awk '/^domain/ {print $2}' $conf)
26+
dns=$(awk '/^nameserver/ {printf "%s ",$2}' $conf)
27+
elif fqdn="$(get_fqdn)" && [ -n "$fqdn" ]; then
28+
domain="$fqdn"
29+
fi
30+
31+
if [ -n "$DOMAINNAME" ]; then
32+
domain="$DOMAINNAME"
33+
fi
34+
35+
if [ -n "$NAMESERVERS" ] || [ $# -gt 0 ];then
36+
dns="$NAMESERVERS"
37+
fi
38+
39+
if [ -n "$domain" ]; then
40+
mkdir -p "${conf%/*}"
41+
echo "search $domain" > $conf
42+
fi
43+
44+
if [ -n "$dns" ] || [ $# -gt 0 ] && [ -f "$conf" ]; then
45+
sed -i -e '/^nameserver/d' $conf
46+
fi
47+
for i in $dns $@; do
48+
mkdir -p "${conf%/*}"
49+
echo "nameserver $i" >> $conf
50+
done

files/static-network.sh

+138
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
#!/bin/sh
2+
3+
# This script is intended to be run on the HookOS/Linuxkit host so it must use /bin/sh.
4+
# No other shells are available on the host.
5+
6+
# this script will statically configure a single network interface based on the ipam= parameter
7+
# passed in the kernel command line. The ipam parameter is a colon separated string with the following fields:
8+
# ipam=<mac-address>:<vlan-id>:<ip-address>:<netmask>:<gateway>:<hostname>:<dns>:<search-domains>:<ntp>
9+
# 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
10+
# the mac address format requires it to be hyphen separated.
11+
12+
exec 3>&1 4>&2
13+
trap 'exec 2>&4 1>&3' 0 1 2 3
14+
exec 1>/var/log/network_config.log 2>&1
15+
16+
set -xeuo pipefail
17+
18+
# Define the location of the interfaces file
19+
INTERFACES_FILE="/var/run/network/interfaces"
20+
21+
parse_ipam_from_cmdline() {
22+
local cmdline
23+
local ipam_value
24+
25+
# Read the contents of /proc/cmdline
26+
cmdline=$(cat /proc/cmdline)
27+
28+
# Use grep to find the ipam= parameter and awk to extract its value
29+
ipam_value=$(echo "$cmdline" | grep -o 'ipam=[^ ]*' | awk -F= '{print $2}')
30+
31+
# Check if ipam= parameter was found
32+
if [ -n "$ipam_value" ]; then
33+
echo "$ipam_value"
34+
return 0
35+
else
36+
echo "ipam= parameter not found in /proc/cmdline" >&2
37+
return 1
38+
fi
39+
}
40+
41+
# Function to get interface name from MAC address
42+
# TODO(jacobweinstock): if a vlan id is provided we should match for the vlan interface
43+
get_interface_name() {
44+
local mac=$1
45+
for interface in /sys/class/net/*; do
46+
if [ -f "$interface/address" ]; then
47+
if [ "$(cat "$interface/address")" == "$mac" ]; then
48+
echo "$(basename "$interface")"
49+
return 0
50+
fi
51+
fi
52+
done
53+
return 1
54+
}
55+
56+
convert_hyphen_to_colon() {
57+
echo "$1" | tr '-' ':'
58+
}
59+
60+
ipam=$(parse_ipam_from_cmdline)
61+
if [ $? -ne 0 ]; then
62+
echo "Failed to get IPAM value, not statically configuring network"
63+
cat /proc/cmdline
64+
exit 0
65+
fi
66+
echo "IPAM value: $ipam"
67+
68+
mkdir -p $(dirname "$INTERFACES_FILE")
69+
70+
# Parse the IPAM string
71+
IFS=':' read -r mac vlan_id ip netmask gateway hostname dns search_domains ntp <<EOF
72+
${ipam}
73+
EOF
74+
75+
# Check for required fields
76+
if [ -z "$mac" ] || [ -z "$ip" ] || [ -z "$netmask" ] || [ -z "$dns" ]; then
77+
echo "Error: MAC address, IP address, netmask, and DNS are required."
78+
echo "$ipam"
79+
exit 1
80+
fi
81+
82+
# convert Mac address to colon separated format
83+
mac=$(convert_hyphen_to_colon "$mac")
84+
85+
# convert , (comma) separated values to space separated values
86+
dns=$(echo "$dns" | tr ',' ' ')
87+
search_domains=$(echo "$search_domains" | tr ',' ' ')
88+
ntp=$(echo "$ntp" | tr ',' ' ')
89+
90+
# Get interface name
91+
interface=$(get_interface_name "$mac")
92+
if [ -z "$interface" ]; then
93+
echo "Error: No interface found with MAC address $mac"
94+
exit 1
95+
fi
96+
97+
# Start writing to the interfaces file
98+
{
99+
echo "# Static Network configuration for $interface"
100+
echo ""
101+
echo "auto $interface"
102+
103+
if [ -n "$vlan_id" ]; then
104+
echo "iface $interface inet manual"
105+
echo ""
106+
echo "auto $interface.$vlan_id"
107+
echo "iface $interface.$vlan_id inet static"
108+
else
109+
echo "iface $interface inet static"
110+
fi
111+
112+
echo " address $ip"
113+
echo " netmask $netmask"
114+
115+
[ -n "$gateway" ] && echo " gateway $gateway"
116+
[ -n "$hostname" ] && echo " hostname $hostname"
117+
118+
if [ -n "$dns" ]; then
119+
echo " dns-nameserver $dns"
120+
fi
121+
122+
if [ -n "$search_domains" ]; then
123+
echo " dns-search $search_domains"
124+
fi
125+
126+
if [ -n "$ntp" ]; then
127+
echo " ntp-servers $ntp"
128+
fi
129+
130+
} > "$INTERFACES_FILE"
131+
132+
echo "Network configuration has been written to $INTERFACES_FILE"
133+
134+
# Run ifup on the interface
135+
ifup -v -a -i "$INTERFACES_FILE"
136+
137+
# setup DNS
138+
ROOT=/run/resolvconf/ setup-dns -d "$search_domains" "$dns"

files/vlan.sh

+42-31
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,74 @@
1-
#!/bin/bash
1+
#!/bin/sh
2+
3+
# This script is intended to be run on the HookOS/Linuxkit host so it must use /bin/sh.
4+
# No other shells are available on the host.
5+
6+
exec 3>&1 4>&2
7+
trap 'exec 2>&4 1>&3' 0 1 2 3
8+
exec 1>/var/log/vlan.log 2>&1
29

310
set -e # exit on error
411

512
# This script will set up VLAN interfaces if `vlan_id=xxxx` in `/proc/cmdline` has a value.
613
# It will use the MAC address specified in `hw_addr=` to find the interface to add the VLAN to.
714

8-
function parse_with_regex_power() {
9-
declare stdin_data cmdline_rest
10-
stdin_data="$(cat)" # read stdin
11-
declare search_argument="${1}"
12-
declare normal_matcher="([a-zA-Z0-9/\\@#\$%^&\!*\(\)'\"=:,._-]+)"
13-
declare quoted_matcher="\"([a-zA-Z0-9/\\@#\$%^&\!*\(\)',=: ._-]+)\""
14-
[ $# -gt 1 ] && normal_matcher="$2" && quoted_matcher="$2"
15-
cmdline_rest="$(printf '%s' "$stdin_data" | sed -rn "s/.* ?${search_argument}=${normal_matcher} ?(.*)+?/\1/p")"
16-
if echo "$cmdline_rest" | grep -Eq '^"'; then
17-
cmdline_rest="$(printf "%s\n" "$stdin_data" | sed -rn "s/.* ?${search_argument}=${quoted_matcher} ?(.*)+?/\1/p")"
18-
fi
19-
printf "%s\n" "$cmdline_rest"
20-
}
15+
parse_from_cmdline() {
16+
local key="${1}"
17+
local cmdline
18+
local ipam_value
2119

22-
function parse_kernel_cmdline_for() {
23-
declare result
24-
# shellcheck disable=SC2002
25-
result=$(cat /proc/cmdline | parse_with_regex_power "$@")
26-
if [ -z "${result}" ]; then
27-
return 1
28-
else
29-
printf "%s" "$result"
30-
fi
20+
# Read the contents of /proc/cmdline
21+
cmdline=$(cat /proc/cmdline)
22+
23+
# Use grep to find the ipam= parameter and awk to extract its value
24+
value=$(echo "$cmdline" | grep -o "${key}=[^ ]*" | awk -F= '{print $2}')
25+
26+
# Check if parameter was found
27+
if [ -n "$value" ]; then
28+
echo "$value"
29+
return 0
30+
else
31+
echo "${key}= parameter not found in /proc/cmdline" >&2
32+
return 1
33+
fi
3134
}
3235

33-
function kernel_cmdline_exists() {
34-
parse_kernel_cmdline_for "$@" > /dev/null
36+
get_interface_name() {
37+
local mac=$1
38+
for interface in /sys/class/net/*; do
39+
if [ -f "$interface/address" ]; then
40+
if [ "$(cat "$interface/address")" == "$mac" ]; then
41+
echo "$(basename "$interface")"
42+
return 0
43+
fi
44+
fi
45+
done
46+
return 1
3547
}
3648

3749
function add_vlan_interface() {
3850
# check if vlan_id are set in the kernel commandline, otherwise return.
39-
if ! kernel_cmdline_exists vlan_id; then
51+
if ! parse_from_cmdline vlan_id; then
4052
echo "No vlan_id=xxxx set in kernel commandline; no VLAN handling." >&2
4153
return
4254
fi
4355

4456
# check if hw_addr are set in the kernel commandline, otherwise return.
45-
if ! kernel_cmdline_exists hw_addr; then
57+
if ! parse_from_cmdline hw_addr; then
4658
echo "No hw_addr=xx:xx:xx:xx:xx:xx set in kernel commandline." >&2
4759
fi
4860

4961
echo "Starting VLAN handling, parsing..." >&2
5062

51-
declare vlan_id hw_addr
52-
vlan_id="$(parse_kernel_cmdline_for vlan_id)"
53-
hw_addr="$(parse_kernel_cmdline_for hw_addr)"
63+
vlan_id="$(parse_from_cmdline vlan_id)"
64+
hw_addr="$(parse_from_cmdline hw_addr)"
5465

5566
echo "VLAN handling - vlan_id: '${vlan_id}', hw_addr: '${hw_addr}'" >&2
5667

5768
if [ -n "$vlan_id" ]; then
5869
if [ -n "$hw_addr" ]; then
5970
echo "VLAN handling - vlan_id: '${vlan_id}', hw_addr: '${hw_addr}', searching for interface..." >&2
60-
ifname="$(ip -br link | awk '$3 ~ /'"${hw_addr}"'/ {print $1}')"
71+
ifname="$(get_interface_name ${hw_addr})"
6172
echo "VLAN handling - vlan_id: '${vlan_id}', hw_addr: '${hw_addr}', found interface: '${ifname}'" >&2
6273
else
6374
echo "VLAN handling - vlan_id: '${vlan_id}', hw_addr: '${hw_addr}', no hw_addr found in kernel commandline; default ifname to eth0." >&2

images/hook-ip/Dockerfile

-23
This file was deleted.

0 commit comments

Comments
 (0)