Skip to content

Commit 73830a0

Browse files
committed
improvement(network-legacy): change ip=any to mean dual stack DHCP
systemd-network-generator tries to support the same IP configuration syntax as Dracut. One difference is that Dracut supports multiple comma-separated autoconf values (e.g. `dhcp,dhcp6`) whereas systemd only permits one. Fixing this in systemd may not be straightforward. This matters because Afterburn can emit `dhcp,dhcp6` for KubeVirt, and it is now being adapted to support systemd-network-generator as well as Dracut's network-legacy module. Even if systemd-network-generator did support multiple values, the behaviour would not be the same. Both systemd-networkd and NetworkManager always try both IPv4 and IPv6, normally requiring just one of them to work, whereas network-legacy will not even try IPv6 if IPv4 works, or vice-versa depending on the order given. It is not currently possible to make network-legacy always try both. To workaround the lack of multi-value support in systemd and to mirror how systemd and NetworkManager behave, I am changing `ip=any` to always try both protocols, requiring just one to work. They are tried in parallel to avoid the cost of a double timeout. Despite the name, this is currently treated by network-legacy as IPv4-only. The history of ifup.sh reveals that `any` was supported before IPv6 was, and that IPv6 was initially added with "preliminary" support. Perhaps the author didn't want to change the behaviour of `any` until that support was better established and then forgot, or perhaps it was merely an oversight. systemd-network-generator also treats `ip=on` as dual stack, but that would be a more controversial change. Users may not expect IPv6 alone to be considered successful. NetworkManager even treats `ip=dhcp` as dual stack, but I feel that is wrong. With `ip=any`, the intent is clearer, and this at least gives us one option where the behaviour is consistent. Much of this change is adjusting the dhclient PID and lease paths to be protocol-specific so that IPv4 and IPv6 instances can be run simultaneously. There were no tests for explicit IP configuration at all, but I felt it was important to have this covered, so some have been added. To avoid unnecessary overhead, this spins up just one VM with several differently configured interfaces. The module changes were mostly written by me with a little help from AI. The tests were entirely written by AI. Signed-off-by: James Le Cuirot <jlecuirot@microsoft.com>
1 parent a83d800 commit 73830a0

14 files changed

Lines changed: 480 additions & 74 deletions

File tree

.github/workflows/daily-network-legacy.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ jobs:
4242
- gentoo:latest
4343
test:
4444
- "50"
45+
- "51"
4546
- "60"
4647
container:
4748
image: ghcr.io/dracut-ng/${{ matrix.container }}

man/dracut.cmdline.7.adoc

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -607,9 +607,12 @@ options; refer to the documentation of the specific network module in use. For
607607
NetworkManager, see man:nm-initrd-generator[8].
608608
609609
**ip=**__{dhcp|on|any|dhcp6|auto6|either6|link6|single-dhcp}__::
610-
dhcp|on|any::: get ip from dhcp server from all interfaces. If netroot=dhcp,
611-
loop sequentially through all interfaces (eth0, eth1, ...) and use the first
612-
with a valid DHCP root-path.
610+
More than one of the following options can be given, separated by commas.
611+
Each is tried in turn until one succeeds.
612+
613+
dhcp|on::: Get an IP from an IPv4 DHCP server for each interface. If
614+
netroot=dhcp, loop sequentially through all interfaces (eth0, eth1, ...)
615+
and use the first with a valid DHCP root-path.
613616
614617
single-dhcp::: Send DHCP on all available interfaces in parallel, as
615618
opposed to one after another. After the first DHCP response is received,
@@ -625,32 +628,26 @@ NetworkManager, see man:nm-initrd-generator[8].
625628
626629
link6::: bring up interface for IPv6 link-local addressing
627630
628-
**ip=**__<interface>__:__{dhcp|on|any|dhcp6|auto6|link6}__[:[__<mtu>__][:__<macaddr>__]]::
631+
any::: Get an IP from an IPv4 and an IPv6 DHCP server on each interface.
632+
Both protocols are tried in parallel, but only one is required to succeed.
633+
634+
**ip=**__<interface>__:__{dhcp|on|any|dhcp6|auto6|either6|link6}__[:[__<mtu>__][:__<macaddr>__]]::
629635
This parameter can be specified multiple times.
630636
+
631637
=====================
632-
dhcp|on|any|dhcp6::: get ip from dhcp server on a specific interface
633-
auto6::: do IPv6 autoconfiguration
634-
link6::: bring up interface for IPv6 link local address
635638
<macaddr>::: optionally **set** <macaddr> on the <interface>. This
636639
cannot be used in conjunction with the **ifname** argument for the
637640
same <interface>.
638641
=====================
639642
640-
**ip=**__<client-IP>__:[__<peer>__]:__<gateway-IP>__:__<netmask>__:__<client_hostname>__:__<interface>__:__{none|off|dhcp|on|any|dhcp6|auto6|ibft}__[:[__<mtu>__][:__<macaddr>__]]::
643+
**ip=**__<client-IP>__:[__<peer>__]:__<gateway-IP>__:__<netmask>__:__<client_hostname>__:__<interface>__:__{none|off|dhcp|on|any|dhcp6|auto6|either6|link6|ibft}__[:[__<mtu>__][:__<macaddr>__]]::
641644
explicit network configuration. If you want do define a IPv6 address, put it
642645
in brackets (e.g. [2001:DB8::1]). This parameter can be specified multiple
643646
times. __<peer>__ is optional and is the address of the remote endpoint
644647
for pointopoint interfaces and it may be followed by a slash and a decimal
645648
number, encoding the network prefix length.
646-
+
647-
=====================
648-
<macaddr>::: optionally **set** <macaddr> on the <interface>. This
649-
cannot be used in conjunction with the **ifname** argument for the
650-
same <interface>.
651-
=====================
652649
653-
**ip=**__<client-IP>__:[__<peer>__]:__<gateway-IP>__:__<netmask>__:__<client_hostname>__:__<interface>__:__{none|off|dhcp|on|any|dhcp6|auto6|ibft}__[:[__<dns1>__][:__<dns2>__]]::
650+
**ip=**__<client-IP>__:[__<peer>__]:__<gateway-IP>__:__<netmask>__:__<client_hostname>__:__<interface>__:__{none|off|dhcp|on|any|dhcp6|auto6|either6|link6|ibft}__[:[__<dns1>__][:__<dns2>__]]::
654651
explicit network configuration. If you want do define a IPv6 address, put it
655652
in brackets (e.g. [2001:DB8::1]). This parameter can be specified multiple
656653
times. __<peer>__ is optional and is the address of the remote endpoint

modules.d/35network-legacy/dhclient-script.sh

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -75,12 +75,12 @@ setup_interface() {
7575
fi
7676

7777
if getargbool 1 rd.peerdns; then
78-
[ -n "${search}${domain}" ] && echo "search $search $domain" > /tmp/net."$netif".resolv.conf
78+
[ -n "${search}${domain}" ] && echo "search $search $domain" > /tmp/net."$netif".IPv4.resolv.conf
7979
if [ -n "$namesrv" ]; then
8080
for s in $namesrv; do
8181
echo nameserver "$s"
8282
done
83-
fi >> /tmp/net."$netif".resolv.conf
83+
fi >> /tmp/net."$netif".IPv4.resolv.conf
8484
fi
8585
# Note: hostname can be fqdn OR short hostname, so chop off any
8686
# trailing domain name and explicitly add any domain if set.
@@ -107,12 +107,12 @@ setup_interface6() {
107107
${preferred_lft:+preferred_lft ${preferred_lft}}
108108

109109
if getargbool 1 rd.peerdns; then
110-
[ -n "${search}${domain}" ] && echo "search $search $domain" > /tmp/net."$netif".resolv.conf
110+
[ -n "${search}${domain}" ] && echo "search $search $domain" > /tmp/net."$netif".IPv6.resolv.conf
111111
if [ -n "$namesrv" ]; then
112112
for s in $namesrv; do
113113
echo nameserver "$s"
114114
done
115-
fi >> /tmp/net."$netif".resolv.conf
115+
fi >> /tmp/net."$netif".IPv6.resolv.conf
116116
fi
117117

118118
# Note: hostname can be fqdn OR short hostname, so chop off any
@@ -237,20 +237,26 @@ case $reason in
237237
fi
238238
unset layer2
239239
setup_interface
240+
241+
# Classless static routes (DHCP option 121) are IPv4-only. Record them
242+
# alongside the default route in the per-interface gw file, which
243+
# setup_net sources, rather than embedding them in the setup_net hook
244+
# below (which a concurrent IPv6 lease would overwrite).
245+
if [ -n "$new_classless_static_routes" ]; then
246+
OLDIFS="$IFS"
247+
IFS=".$IFS"
248+
parse_option_121 "$new_classless_static_routes" >> /tmp/net."$netif".gw
249+
IFS="$OLDIFS"
250+
fi
251+
240252
set | while read -r line || [ -n "$line" ]; do
241253
[ "${line#new_}" = "$line" ] && continue
242254
echo "$line"
243-
done > /tmp/dhclient."$netif".dhcpopts
255+
done > /tmp/dhclient."$netif".IPv4.dhcpopts
244256

245257
{
246258
echo '. /lib/net-lib.sh'
247259
echo "setup_net $netif"
248-
if [ -n "$new_classless_static_routes" ]; then
249-
OLDIFS="$IFS"
250-
IFS=".$IFS"
251-
parse_option_121 "$new_classless_static_routes"
252-
IFS="$OLDIFS"
253-
fi
254260
echo "source_hook initqueue/online $netif"
255261
[ -e /tmp/net."$netif".manualup ] || echo "/sbin/netroot $netif"
256262
echo "rm -f -- $hookdir/initqueue/setup_net_$netif.sh"
@@ -282,7 +288,7 @@ case $reason in
282288
set | while read -r line || [ -n "$line" ]; do
283289
[ "${line#new_}" = "$line" ] && continue
284290
echo "$line"
285-
done > /tmp/dhclient."$netif".dhcpopts
291+
done > /tmp/dhclient."$netif".IPv6.dhcpopts
286292

287293
{
288294
echo '. /lib/net-lib.sh'

modules.d/35network-legacy/dhcp-multi.sh

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ command -v getarg > /dev/null || . /lib/dracut-lib.sh
1111

1212
netif=$1
1313
do_vlan=$2
14-
arg=$3
14+
_IPv=${3:-4}
15+
_tmp_prefix="/tmp/dhclient.${netif}.IPv${_IPv}"
16+
shift 3
1517

1618
# Run dhclient in parallel
1719
do_dhclient() {
@@ -29,14 +31,14 @@ do_dhclient() {
2931
fi
3032

3133
while [ $_COUNT -lt "$_DHCPRETRY" ]; do
32-
info "Starting dhcp for interface $netif"
33-
dhclient "$arg" \
34+
info "Starting IPv$_IPv dhcp for interface $netif"
35+
dhclient -"${_IPv}" "$@" \
3436
${_timeout:+--timeout "$_timeout"} \
3537
-q \
3638
-1 \
3739
-cf /etc/dhclient.conf \
38-
-pf /tmp/dhclient."$netif".pid \
39-
-lf /tmp/dhclient."$netif".lease \
40+
-pf "${_tmp_prefix}.pid" \
41+
-lf "${_tmp_prefix}.lease" \
4042
"$netif" &
4143
wait $! 2> /dev/null
4244

@@ -51,16 +53,16 @@ do_dhclient() {
5153
# If dhclient exited before wait was called, or it was killed by
5254
# another thread for interface whose DHCP succeeded, then it will not
5355
# find the process with that pid and return error code 127. In that
54-
# case we need to check if /tmp/dhclient.$netif.lease exists. If it
55-
# does, it means dhclient finished executing before wait was called,
56-
# and it was successful (return 0). If /tmp/dhclient.$netif.lease
57-
# does not exist, then it means dhclient was killed by another thread
58-
# or it finished execution but failed dhcp on that interface.
56+
# case we need to check if /tmp/dhclient.$netif.IPvX.lease exists. If
57+
# it does, it means dhclient finished executing before wait was
58+
# called, and it was successful (return 0). If the lease file does not
59+
# exist, then it means dhclient was killed by another thread or it
60+
# finished execution but failed dhcp on that interface.
5961

6062
if [ $retv -eq 127 ]; then
61-
read -r pid < /tmp/dhclient."$netif".pid
63+
read -r pid < "${_tmp_prefix}.pid"
6264
info "PID $pid was not found by wait for $netif"
63-
if [ -e /tmp/dhclient."$netif".lease ]; then
65+
if [ -e "${_tmp_prefix}.lease" ]; then
6466
info "PID $pid not found but DHCP successful on $netif"
6567
return 0
6668
fi
@@ -69,14 +71,13 @@ do_dhclient() {
6971
_COUNT=$((_COUNT + 1))
7072
[ $_COUNT -lt "$_DHCPRETRY" ] && sleep 1
7173
done
72-
warn "dhcp for interface $netif failed"
73-
# nuke those files since we failed; we might retry dhcp again if it's e.g.
74-
# `ip=dhcp,dhcp6` and we check for the PID file earlier
75-
rm -f /tmp/dhclient."$netif".pid /tmp/dhclient."$netif".lease
74+
warn "IPv$_IPv dhcp for interface $netif failed"
75+
# nuke those files since we failed
76+
rm -f "${_tmp_prefix}.pid" "${_tmp_prefix}.lease"
7677
return 1
7778
}
7879

79-
do_dhclient
80+
do_dhclient "$@"
8081
ret=$?
8182

8283
# setup nameserver
@@ -111,22 +112,22 @@ if [ $ret -eq 0 ]; then
111112

112113
if ln -s "$netif" "$IFNETFILE" 2> /dev/null; then
113114
intf=$(readlink "$IFNETFILE")
114-
if [ -e /tmp/dhclient."$intf".lease ]; then
115-
info "DHCP successful on interface $intf"
115+
if [ -e "/tmp/dhclient.$intf.IPv$_IPv.lease" ]; then
116+
info "IPv$_IPv DHCP successful on interface $intf"
116117
# Kill all existing dhclient calls for other interfaces, since we
117118
# already got one successful interface
118119

119-
read -r npid < /tmp/dhclient."$netif".pid
120+
read -r npid < "${_tmp_prefix}.pid"
120121
pidlist=$(pgrep dhclient)
121122
for pid in $pidlist; do
122123
[ "$pid" -eq "$npid" ] && continue
123124
kill -9 "$pid" > /dev/null 2>&1
124125
done
125126
else
126-
echo "ERROR! $IFNETFILE exists but /tmp/dhclient.$intf.lease does not exist!!!"
127+
echo "ERROR! $IFNETFILE exists but /tmp/dhclient.$intf.IPv$_IPv.lease does not exist!!!"
127128
fi
128129
else
129-
info "DHCP success on $netif, and also on $intf"
130+
info "IPv$_IPv DHCP success on $netif, and also on $intf"
130131
exit 0
131132
fi
132133
exit $ret

0 commit comments

Comments
 (0)