]> git.ipfire.org Git - thirdparty/systemd.git/blame - test/test-network-generator-conversion.sh
test: add test case for network.conf.* credential
[thirdparty/systemd.git] / test / test-network-generator-conversion.sh
CommitLineData
fbaa1137 1#!/usr/bin/env bash
8f5bcd61 2# SPDX-License-Identifier: LGPL-2.1-or-later
6bc5de53
FS
3# shellcheck disable=SC2235
4set -eux
5set -o pipefail
fbaa1137 6
6bc5de53
FS
7# TODO/FIXME:
8# - we should probably have something like "udevadm verify" but for .network files
9# (networkctl verify?) so we can check that all directives are in correct sections
10# - according to dracut.cmdline(7) <peer> address can also be followed by /CIDR,
11# but this doesn't seem to work with sd-network-generator
12
13if [[ -n "${1:-}" ]]; then
14 GENERATOR_BIN=$1
fbaa1137 15elif [[ -x /usr/lib/systemd/systemd-network-generator ]]; then
6bc5de53 16 GENERATOR_BIN=/usr/lib/systemd/systemd-network-generator
fbaa1137 17elif [[ -x /lib/systemd/systemd-network-generator ]]; then
6bc5de53 18 GENERATOR_BIN=/lib/systemd/systemd-network-generator
fbaa1137
ZJS
19else
20 exit 1
21fi
22
6bc5de53
FS
23# See: https://github.com/systemd/systemd/pull/29888#issuecomment-1796187440
24unset NOTIFY_SOCKET
25
26WORK_DIR="$(mktemp --directory --tmpdir "test-network-generator-conversion.XXXXXX")"
27# shellcheck disable=SC2064
28trap "rm -rf '$WORK_DIR'" EXIT
29
30# Convert octal netmask to CIDR notation (e.g. 255.255.255.0 => 24)
31netmask_to_cidr() (
32 set +x
33
34 local netmask="${1:?}"
35 local x bits=0
36
37 # shellcheck disable=SC2086
38 x="0$(printf "%o" ${netmask//./ })"
39 while [[ "$x" -gt 0 ]]; do
40 ((bits += x % 2))
41 ((x >>= 1))
42 done
43
44 echo "$bits"
45)
46
47run_network_generator() {
48 local stderr
49
50 rm -rf "${WORK_DIR:?}"/*
51 stderr="$WORK_DIR/stderr"
3cb61808 52 if ! SYSTEMD_LOG_LEVEL="info" "$GENERATOR_BIN" --root "$WORK_DIR" 2>"$stderr"; then
6bc5de53 53 echo >&2 "Generator failed when parsing $SYSTEMD_PROC_CMDLINE"
78643f26 54 cat >&2 "$stderr"
6bc5de53
FS
55 return 1
56 fi
57
58 if [[ -s "$stderr" ]]; then
59 echo >&2 "Generator generated unexpected messages on stderr"
78643f26 60 cat >&2 "$stderr"
6bc5de53
FS
61 return 1
62 fi
63
64 ls -l "$WORK_DIR/run/systemd/network/"
65
66 rm -f "$stderr"
67 return 0
68}
69
70check_dhcp() {
71 local dhcp="${1:?}"
72 local network_file="${2:?}"
73
74 case "$dhcp" in
75 dhcp)
76 grep -q "^DHCP=ipv4$" "$network_file"
77 ;;
78 dhcp6)
79 grep -q "^DHCP=ipv6$" "$network_file"
80 ;;
81 on|any)
82 grep -q "^DHCP=yes$" "$network_file"
83 ;;
84 none|off)
85 grep -q "^DHCP=no$" "$network_file"
86 grep -q "^LinkLocalAddressing=no$" "$network_file"
87 grep -q "^IPv6AcceptRA=no$" "$network_file"
88 ;;
89 auto6|ibft)
90 grep -q "^DHCP=no$" "$network_file"
91 ;;
92 either6)
93 grep -q "^DHCP=ipv6$" "$network_file"
94 ;;
95 link6)
96 grep -q "^DHCP=no$" "$network_file"
97 grep -q "^LinkLocalAddressing=ipv6$" "$network_file"
98 grep -q "^IPv6AcceptRA=no$" "$network_file"
99 ;;
100 link-local)
101 grep -q "^DHCP=no$" "$network_file"
102 grep -q "^LinkLocalAddressing=yes$" "$network_file"
103 grep -q "^IPv6AcceptRA=no$" "$network_file"
104 ;;
105 *)
106 echo >&2 "Invalid assignment $cmdline"
107 return 1
108 esac
109
110 return 0
111}
112
113# Check the shortest ip= variant, i.e.:
114# ip={dhcp|on|any|dhcp6|auto6|either6|link6|link-local}
115#
116# Note:
117# - dracut also supports single-dhcp
118# - link-local is supported only by systemd-network-generator
119check_one_dhcp() {
120 local cmdline="${1:?}"
121 local dhcp="${cmdline#ip=}"
122 local network_file
123
124 SYSTEMD_LOG_LEVEL=debug SYSTEMD_PROC_CMDLINE="$cmdline" run_network_generator
125 network_file="${WORK_DIR:?}/run/systemd/network/71-default.network"
126 cat "$network_file"
127
128 check_dhcp "$dhcp" "$network_file"
129
130 return 0
131}
132
133# Similar to the previous one, but with slightly more fields:
134# ip=<interface>:{dhcp|on|any|dhcp6|auto6|link6|link-local}[:[<mtu>][:<macaddr>]]
135#
136# Same notes apply as well.
137check_one_interface_dhcp() {
138 local cmdline="${1:?}"
139 local ifname dhcp mtu mac network_file
140
141 IFS=":" read -r ifname dhcp mtu mac <<< "${cmdline#ip=}"
142
143 SYSTEMD_LOG_LEVEL=debug SYSTEMD_PROC_CMDLINE="$cmdline" run_network_generator
144 network_file="${WORK_DIR:?}/run/systemd/network/70-$ifname.network"
145 cat "$network_file"
fbaa1137 146
6bc5de53
FS
147 grep -q "^Name=$ifname$" "$network_file"
148 check_dhcp "$dhcp" "$network_file"
149 [[ -n "$mtu" ]] && grep -q "^MTUBytes=$mtu$" "$network_file"
150 [[ -n "$mac" ]] && grep -q "^MACAddress=$mac$" "$network_file"
fbaa1137 151
6bc5de53
FS
152 return 0
153}
fbaa1137 154
6bc5de53
FS
155# Check the "long" ip= formats, i.e:
156# ip=<client-IP>:[<peer>]:<gateway-IP>:<netmask>:<client_hostname>:<interface>:{none|off|dhcp|on|any|dhcp6|auto6|ibft}[:[<mtu>][:<macaddr>]
157# ip=<client-IP>:[<peer>]:<gateway-IP>:<netmask>:<client_hostname>:<interface>:{none|off|dhcp|on|any|dhcp6|auto6|ibft}[:[<dns1>][:<dns2>]]
158check_one_long() {
159 local cmdline="${1:?}"
160 local ip peer gateway netmask hostname ifname dhcp arg1 arg2 network_file cidr stderr tmp
fbaa1137 161
6bc5de53
FS
162 # To make parsing a bit easier when IPv6 is involved, replace all colons between [] with #, ...
163 tmp="$(echo "${cmdline#ip=}" | sed -re ':l; s/(\[[^]:]*):/\1#/; tl')"
164 # ... drop the now unnecessary [] and split the string into colon separated fields as usual, ...
165 IFS=":" read -r ip peer gateway netmask hostname ifname dhcp arg1 arg2 <<<"${tmp//[\[\]]}"
166 # ... and then replace # back to colons for fields that might contain an IPv6 address.
167 ip="${ip//#/:}"
168 peer="${peer//#/:}"
169 gateway="${gateway//#/:}"
170 arg1="${arg1//#/:}"
171 arg2="${arg2//#/:}"
172
173 SYSTEMD_LOG_LEVEL=debug SYSTEMD_PROC_CMDLINE="$cmdline" run_network_generator
174
175 if [[ -n "$ifname" ]]; then
176 network_file="${WORK_DIR:?}/run/systemd/network/70-$ifname.network"
177 grep -q "^Name=$ifname$" "$network_file"
178 else
179 network_file="${WORK_DIR:?}/run/systemd/network/71-default.network"
180 grep -q "^Kind=!\*$" "$network_file"
181 fi
182
183 cat "$network_file"
184
185 if [[ -n "$ip" && -n "$netmask" ]]; then
186 # The "ip" and "netmask" fields are merged together into an IP/CIDR value
187 if [[ "$netmask" =~ ^[0-9]+$ ]]; then
188 cidr="$netmask"
189 else
190 cidr="$(netmask_to_cidr "$netmask")"
fbaa1137 191 fi
6bc5de53
FS
192
193 grep -q "^Address=$ip/$cidr$" "$network_file"
194 else
195 (! grep -q "^Address=" "$network_file")
196 fi
197 # If the "dhcp" field is empty, it defaults to "off"
198 [[ -z "$dhcp" ]] && dhcp="off"
199 [[ -n "$peer" ]] && grep -q "^Peer=$peer$" "$network_file"
200 [[ -n "$gateway" ]] && grep -q "^Gateway=$gateway$" "$network_file"
201 [[ -n "$hostname" ]] && grep -q "^Hostname=$hostname$" "$network_file"
202 check_dhcp "$dhcp" "$network_file"
203
204 # If the first optional argument is empty, assume the first variant
205 # See: https://github.com/dracutdevs/dracut/blob/4d594210d6ef4f04a9dbadacea73e9461ded352d/modules.d/40network/net-lib.sh#L533
206 if [[ -z "$arg1" || "$arg1" =~ ^[0-9]+$ ]]; then
207 # => [:[<mtu>][:<macaddr>]
208 [[ -n "$arg1" ]] && grep -q "^MTUBytes=$arg1$" "$network_file"
209 [[ -n "$arg2" ]] && grep -q "^MACAddress=$arg2$" "$network_file"
210 else
211 # => [:[<dns1>][:<dns2>]]
212 grep -q "^DNS=$arg1$" "$network_file"
213 [[ -n "$arg2" ]] && grep -q "^DNS=$arg2$" "$network_file"
214 fi
215
216 return 0
217}
218
219# Check if the generated .network files match the expected stored ones
220TEST_DATA="$(dirname "$0")/testdata/test-network-generator-conversion"
221for f in "$TEST_DATA"/test-*.input; do
222 fname="${f##*/}"
223 out="$(mktemp --directory "${WORK_DIR:?}/${fname%%.input}.XXX")"
224
225 # shellcheck disable=SC2046
226 "$GENERATOR_BIN" --root "$out" -- $(cat "$f")
227
228 if ! diff -u "$out/run/systemd/network" "${f%.input}.expected"; then
78643f26 229 echo >&2 "**** Unexpected output for $f"
6bc5de53
FS
230 exit 1
231 fi
232
233 rm -rf "${out:?}"
234done
235
236# Now generate bunch of .network units on the fly and check if they contain expected
237# directives & values
238
239# ip={dhcp|on|any|dhcp6|auto6|either6|link6|link-local}
240for dhcp in dhcp on any dhcp6 auto6 either6 link6 link-local off none ibft; do
241 check_one_dhcp "ip=$dhcp"
242done
243
244# ip=<interface>:{dhcp|on|any|dhcp6|auto6|link6|link-local}[:[<mtu>][:<macaddr>]]
245COMMAND_LINES=(
246 "ip=foo:dhcp"
247 "ip=bar:dhcp6"
248 "ip=linklocal99:link-local"
249 "ip=baz1:any:666"
250 "ip=baz1:any:128:52:54:00:a7:8f:ac"
251)
252for cmdline in "${COMMAND_LINES[@]}"; do
253 check_one_interface_dhcp "$cmdline"
254done
255
256# ip=<client-IP>:[<peer>]:<gateway-IP>:<netmask>:<client_hostname>:<interface>:{none|off|dhcp|on|any|dhcp6|auto6|ibft}[:[<mtu>][:<macaddr>]
257# ip=<client-IP>:[<peer>]:<gateway-IP>:<netmask>:<client_hostname>:<interface>:{none|off|dhcp|on|any|dhcp6|auto6|ibft}[:[<dns1>][:<dns2>]]
258COMMAND_LINES=(
259 "ip=1.2.3.4:2.3.4.5:1.2.3.1:255.255.255.0:hello-world.local:dummy99:off"
260 "ip=1.2.3.4:2.3.4.5:1.2.3.1:24:hello-world.local:dummy99:off"
261 "ip=1.2.3.4:2.3.4.5:1.2.3.1:255.255.255.0:hello-world.local:dummy99:off:123"
262 "ip=1.2.3.4:2.3.4.5:1.2.3.1:255.255.255.0:hello-world.local:dummy99:off:123:52:54:00:a7:8f:ac"
263 "ip=1.2.3.4:2.3.4.5:1.2.3.1:255.255.255.0:hello-world.local:dummy99:off::52:54:00:a7:8f:ac"
264 "ip=1.2.3.4:2.3.4.5:1.2.3.1:255.255.255.0:hello-world.local:dummy99:off::"
265 "ip=1.2.3.4:2.3.4.5:1.2.3.1:255.255.255.0:hello-world.local:dummy99:off:1.2.3.2"
266 "ip=1.2.3.4:2.3.4.5:1.2.3.1:255.255.255.0:hello-world.local:dummy99:off:1.2.3.2:1.2.3.3"
267 "ip=192.168.0.2::192.168.0.1:255.255.128.0::foo1:off"
268 "ip=192.168.0.2::192.168.0.1:17::foo1:off"
269 "ip=10.0.0.1:::255.255.255.0::foo99:off"
270 "ip=[fdef:c400:bd01:1096::2]::[fdef:c400:bd01:1096::1]:64::ipv6:off"
271 "ip=[fdef:c400:bd01:1096::2]:[fdef:c400:bd01:1096::99]::64::ipv6:off"
272 "ip=[fdef:c400:bd01:1096::2]::[fdef:c400:bd01:1096::1]:64::ipv6:off:666"
273 "ip=[fdef:c400:bd01:1096::2]::[fdef:c400:bd01:1096::1]:64::ipv6:off:666:52:54:00:a7:8f:ac"
274 "ip=[fdef:c400:bd01:1096::2]::[fdef:c400:bd01:1096::1]:64::ipv6:off::52:54:00:a7:8f:ac"
275 "ip=[fdef:c400:bd01:1096::2]::[fdef:c400:bd01:1096::1]:64::ipv6:off::"
276 "ip=[fdef:c400:bd01:1096::2]::[fdef:c400:bd01:1096::1]:64::ipv6:off:[fdef:c400:bd01:1096::aaaa]"
277 "ip=[fdef:c400:bd01:1096::2]::[fdef:c400:bd01:1096::1]:64::ipv6:off:[fdef:c400:bd01:1096::aaaa]:[fdef:c400:bd01:1096::bbbb]"
278 "ip=:::::dhcp99:any"
279 "ip=:::::dhcp99:dhcp6:666"
280 "ip=:::::dhcp99:dhcp6:666:52:54:00:a7:8f:ac"
281 "ip=:::::dhcp99:dhcp6:10.0.0.128"
282 "ip=:::::dhcp99:dhcp6:10.0.0.128:10.0.0.129"
816c269e 283 "ip=:::::dhcp99:dhcp6:10.0.0.128:[fdef:c400:bd01:1096::bbbb]"
6bc5de53
FS
284 "ip=::::::any"
285 "ip=::::::ibft"
286)
287for cmdline in "${COMMAND_LINES[@]}"; do
288 check_one_long "$cmdline"
289done
290
291INVALID_COMMAND_LINES=(
292 "ip=foo"
293 "ip=:::::::"
294 "ip=:::::::foo"
295 "ip=10.0.0:::255.255.255.0::foo99:off"
296 "ip=10.0.0.1:::255.255.255::foo99:off"
297 "ip=10.0.0.1:::255.255.255.0:invalid_hostname:foo99:off"
298 "ip=10.0.0.1:::255.255.255.0::verylonginterfacename:off"
a0460dfe 299 "ip=:::::dhcp99:dhcp6:4294967296"
6bc5de53
FS
300 "ip=:::::dhcp99:dhcp6:-1"
301 "ip=:::::dhcp99:dhcp6:666:52:54:00"
302 "ip=fdef:c400:bd01:1096::2::[fdef:c400:bd01:1096::1]:64::ipv6:off:[fdef:c400:bd01:1096::aaaa]"
303 "ip=[fdef:c400:bd01:1096::2]::[fdef:c400:bd01:1096::1]:64::ipv6:off:foo"
304 "ip=[fdef:c400:bd01:1096::2]::[fdef:c400:bd01:1096::1]:64::ipv6:off:[fdef:c400:bd01:1096::aaaa]:foo"
b86f60bf
YW
305 "ip=[fdef:c400:bd01:1096::2]::[fdef:c400:bd01:1096::1]:64::ipv6:off:[fdef:c400:bd01:1096::aaaa]:[fdef:c400:bd01:1096::bbbb]:"
306 "ip=:::::dhcp99:dhcp6:10.0.0.128:10.0.0.129:"
307 "ip=:::::dhcp99:dhcp6:10.0.0.128:[fdef:c400:bd01:1096::bbbb]:"
6bc5de53
FS
308)
309for cmdline in "${INVALID_COMMAND_LINES[@]}"; do
9e6d5879 310 (! SYSTEMD_LOG_LEVEL=debug SYSTEMD_PROC_CMDLINE="$cmdline" "$GENERATOR_BIN" --root "$WORK_DIR")
fbaa1137 311done