]>
Commit | Line | Data |
---|---|---|
fbaa1137 | 1 | #!/usr/bin/env bash |
8f5bcd61 | 2 | # SPDX-License-Identifier: LGPL-2.1-or-later |
6bc5de53 FS |
3 | # shellcheck disable=SC2235 |
4 | set -eux | |
5 | set -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 | ||
13 | if [[ -n "${1:-}" ]]; then | |
14 | GENERATOR_BIN=$1 | |
fbaa1137 | 15 | elif [[ -x /usr/lib/systemd/systemd-network-generator ]]; then |
6bc5de53 | 16 | GENERATOR_BIN=/usr/lib/systemd/systemd-network-generator |
fbaa1137 | 17 | elif [[ -x /lib/systemd/systemd-network-generator ]]; then |
6bc5de53 | 18 | GENERATOR_BIN=/lib/systemd/systemd-network-generator |
fbaa1137 ZJS |
19 | else |
20 | exit 1 | |
21 | fi | |
22 | ||
6bc5de53 FS |
23 | # See: https://github.com/systemd/systemd/pull/29888#issuecomment-1796187440 |
24 | unset NOTIFY_SOCKET | |
25 | ||
26 | WORK_DIR="$(mktemp --directory --tmpdir "test-network-generator-conversion.XXXXXX")" | |
27 | # shellcheck disable=SC2064 | |
28 | trap "rm -rf '$WORK_DIR'" EXIT | |
29 | ||
30 | # Convert octal netmask to CIDR notation (e.g. 255.255.255.0 => 24) | |
31 | netmask_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 | ||
47 | run_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 | ||
70 | check_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 | |
119 | check_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. | |
137 | check_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>]] | |
158 | check_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 | |
220 | TEST_DATA="$(dirname "$0")/testdata/test-network-generator-conversion" | |
221 | for 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:?}" | |
234 | done | |
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} | |
240 | for dhcp in dhcp on any dhcp6 auto6 either6 link6 link-local off none ibft; do | |
241 | check_one_dhcp "ip=$dhcp" | |
242 | done | |
243 | ||
244 | # ip=<interface>:{dhcp|on|any|dhcp6|auto6|link6|link-local}[:[<mtu>][:<macaddr>]] | |
245 | COMMAND_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 | ) | |
252 | for cmdline in "${COMMAND_LINES[@]}"; do | |
253 | check_one_interface_dhcp "$cmdline" | |
254 | done | |
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>]] | |
258 | COMMAND_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 | ) | |
287 | for cmdline in "${COMMAND_LINES[@]}"; do | |
288 | check_one_long "$cmdline" | |
289 | done | |
290 | ||
291 | INVALID_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 | ) |
309 | for cmdline in "${INVALID_COMMAND_LINES[@]}"; do | |
9e6d5879 | 310 | (! SYSTEMD_LOG_LEVEL=debug SYSTEMD_PROC_CMDLINE="$cmdline" "$GENERATOR_BIN" --root "$WORK_DIR") |
fbaa1137 | 311 | done |