Skip to content

Commit a576643

Browse files
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=<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 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 <[email protected]>
1 parent b5560c5 commit a576643

File tree

7 files changed

+252
-46
lines changed

7 files changed

+252
-46
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

linuxkit-templates/hook.template.yaml

+12-10
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
# - HOOK_KERNEL_IMAGE: ${HOOK_KERNEL_IMAGE}
44
# - HOOK_KERNEL_ID: ${HOOK_KERNEL_ID}
55
# - HOOK_KERNEL_VERSION: ${HOOK_KERNEL_VERSION}
6-
# - HOOK_CONTAINER_IP_IMAGE: ${HOOK_CONTAINER_IP_IMAGE}
76
# - HOOK_CONTAINER_BOOTKIT_IMAGE: ${HOOK_CONTAINER_BOOTKIT_IMAGE}
87
# - HOOK_CONTAINER_DOCKER_IMAGE: ${HOOK_CONTAINER_DOCKER_IMAGE}
98
# - HOOK_CONTAINER_MDEV_IMAGE: ${HOOK_CONTAINER_MDEV_IMAGE}
@@ -42,14 +41,6 @@ onboot:
4241
image: linuxkit/modprobe:v1.0.0
4342
command: [ "modprobe", "cdc_ncm" ] # for usb ethernet dongles
4443

45-
- name: vlan
46-
image: "${HOOK_CONTAINER_IP_IMAGE}"
47-
capabilities:
48-
- all
49-
binds.add:
50-
- /etc/ip/vlan.sh:/etc/ip/vlan.sh
51-
command: [ "/etc/ip/vlan.sh" ]
52-
5344
- name: dhcpcd-once
5445
image: linuxkit/dhcpcd:v1.0.0
5546
command: [ "/etc/ip/dhcp.sh", "true" ] # 2nd paramter is one-shot true/false: true for onboot, false for services
@@ -275,10 +266,21 @@ files:
275266
ANSI_COLOR="1;34"
276267
HOME_URL="https://github.com/tinkerbell/hook"
277268
278-
- path: etc/ip/vlan.sh
269+
# Putting scripts in /etc/init.d/ allows them to be run at boot time
270+
- path: etc/init.d/002-vlan.sh
279271
source: "files/vlan.sh"
280272
mode: "0777"
281273

274+
# Putting scripts in /etc/init.d/ allows them to be run at boot time
275+
- path: etc/init.d/003-static-network.sh
276+
source: "files/static-network.sh"
277+
mode: "0777"
278+
279+
# This makes the script available in the host PATH
280+
- path: sbin/setup-dns
281+
source: "files/setup-dns.sh"
282+
mode: "0777"
283+
282284
- path: etc/ip/dhcp.sh
283285
source: "files/dhcp.sh"
284286
mode: "0777"

0 commit comments

Comments
 (0)