diff --git a/dracut.cmdline.7.asc b/dracut.cmdline.7.asc index dc140d7f..0951c0e0 100644 --- a/dracut.cmdline.7.asc +++ b/dracut.cmdline.7.asc @@ -569,11 +569,17 @@ USB Android phone:: * enp0s29u1u2 ===================== -**ip=**__{dhcp|on|any|dhcp6|auto6|either6}__:: +**ip=**__{dhcp|on|any|dhcp6|auto6|either6|single-dhcp}__:: dhcp|on|any::: get ip from dhcp server from all interfaces. If root=dhcp, loop sequentially through all interfaces (eth0, eth1, ...) and use the first with a valid DHCP root-path. + single-dhcp::: Send DHCP on all available interfaces in parallel, as + opposed to one after another. After the first DHCP response is received, + stop DHCP on all other interfaces. This gives the fastest boot time by + using the IP on interface for which DHCP succeeded first during early boot. + Caveat: Does not apply to Network Manager and to SUSE using wicked. + auto6::: IPv6 autoconfiguration dhcp6::: IPv6 DHCP diff --git a/modules.d/35network-legacy/dhcp-multi.sh b/modules.d/35network-legacy/dhcp-multi.sh new file mode 100755 index 00000000..8c58a695 --- /dev/null +++ b/modules.d/35network-legacy/dhcp-multi.sh @@ -0,0 +1,125 @@ +#!/bin/sh +# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*- +# ex: ts=8 sw=4 sts=4 et filetype=sh +# +PATH=/usr/sbin:/usr/bin:/sbin:/bin + +# File to start dhclient requests on different interfaces in parallel + +. /lib/dracut-lib.sh +. /lib/net-lib.sh + +netif=$1 +do_vlan=$2 +arg=$3 + +# Run dhclient in parallel +do_dhclient() { + local _COUNT=0 + local _timeout=$(getargs rd.net.timeout.dhcp=) + local _DHCPRETRY=$(getargs rd.net.dhcp.retry=) + _DHCPRETRY=${_DHCPRETRY:-1} + + while [ $_COUNT -lt $_DHCPRETRY ]; do + info "Starting dhcp for interface $netif" + dhclient $arg \ + ${_timeout:+--timeout $_timeout} \ + -q \ + -1 \ + -cf /etc/dhclient.conf \ + -pf /tmp/dhclient.$netif.pid \ + -lf /tmp/dhclient.$netif.lease \ + $netif & + wait $! 2>/dev/null + + # wait will return the return value of dhclient + retv=$? + + # dhclient and hence wait returned success, 0. + if [ $retv -eq 0 ]; then + return 0 + fi + + # If dhclient exited before wait was called, or it was killed by + # another thread for interface whose DHCP succeeded, then it will not + # find the process with that pid and return error code 127. In that + # case we need to check if /tmp/dhclient.$netif.lease exists. If it + # does, it means dhclient finished executing before wait was called, + # and it was successful (return 0). If /tmp/dhclient.$netif.lease + # does not exist, then it means dhclient was killed by another thread + # or it finished execution but failed dhcp on that interface. + + if [ $retv -eq 127 ]; then + pid=$(cat /tmp/dhclient.$netif.pid) + info "PID $pid was not found by wait for $netif" + if [ -e /tmp/dhclient.$netif.lease ]; then + info "PID $pid not found but DHCP successful on $netif" + return 0 + fi + fi + + _COUNT=$(($_COUNT+1)) + [ $_COUNT -lt $_DHCPRETRY ] && sleep 1 + done + warn "dhcp for interface $netif failed" + # nuke those files since we failed; we might retry dhcp again if it's e.g. + # `ip=dhcp,dhcp6` and we check for the PID file earlier + rm -f /tmp/dhclient.$netif.{pid,lease} + return 1 +} + +do_dhclient +ret=$? + +# setup nameserver +for s in "$dns1" "$dns2" $(getargs nameserver); do + [ -n "$s" ] || continue + echo nameserver $s >> /tmp/net.$netif.resolv.conf +done + +if [ $ret -eq 0 ]; then + > /tmp/net.${netif}.up + + if [ -z "$do_vlan" ] && [ -e /sys/class/net/${netif}/address ]; then + > /tmp/net.$(cat /sys/class/net/${netif}/address).up + fi + + # Check if DHCP also suceeded on another interface before this one. + # We will always use the first one on which DHCP succeeded, by using + # a commom file $IFNETFILE, to synchronize between threads. + # Consider the race condition in which multiple threads + # corresponding to different interfaces may try to read $IFNETFILE + # and find it does not exist; they may all end up thinking they are the + # first to succeed (hence more than one thread may end up writing to + # $IFNETFILE). To take care of this, instead of checking if $IFNETFILE + # exists to determine if we are the first, we create a symbolic link + # in $IFNETFILE, pointing to the interface name ($netif), thus storing + # the interface name in the link pointer. + # Creating a link will fail, if the link already exists, hence kernel + # will take care of allowing only first thread to create link, which + # takes care of the race condition for us. Subsequent threads will fail. + # Also, the link points to the interface name, which will tell us which + # interface succeeded. + + if ln -s $netif $IFNETFILE 2>/dev/null; then + intf=$(readlink $IFNETFILE) + if [ -e /tmp/dhclient.$intf.lease ]; then + info "DHCP successful on interface $intf" + # Kill all existing dhclient calls for other interfaces, since we + # already got one successful interface + + npid=$(cat /tmp/dhclient.$netif.pid) + pidlist=$(pgrep dhclient) + for pid in $pidlist; do + [ "$pid" -eq "$npid" ] && continue + kill -9 $pid >/dev/null 2>&1 + done + else + echo "ERROR! $IFNETFILE exists but /tmp/dhclient.$intf.lease does not exist!!!" + fi + else + info "DHCP success on $netif, and also on $intf" + exit 0 + fi + exit $ret +fi diff --git a/modules.d/35network-legacy/ifup.sh b/modules.d/35network-legacy/ifup.sh index b1ae52ea..130b799f 100755 --- a/modules.d/35network-legacy/ifup.sh +++ b/modules.d/35network-legacy/ifup.sh @@ -23,6 +23,35 @@ if [ "$netif" = "lo" ] ; then exit 0 fi +do_dhcp_parallel() { + # dhclient-script will mark the netif up and generate the online + # event for nfsroot + # XXX add -V vendor class and option parsing per kernel + + [ -e /tmp/dhclient.$netif.pid ] && return 0 + + if ! iface_has_carrier $netif; then + warn "No carrier detected on interface $netif" + return 1 + fi + + bootintf=$(readlink $IFNETFILE) + if [ ! -z $bootintf ] && [ -e /tmp/dhclient.$bootintf.lease ]; then + info "DHCP already succeeded for $bootintf, exiting for $netif" + return 1; + fi + + if [ ! -e /run/NetworkManager/conf.d/10-dracut-dhclient.conf ]; then + mkdir -p /run/NetworkManager/conf.d + echo '[main]' > /run/NetworkManager/conf.d/10-dracut-dhclient.conf + echo 'dhcp=dhclient' >>/run/NetworkManager/conf.d/10-dracut-dhclient.conf + fi + + chmod +x /sbin/dhcp-multi.sh + /sbin/dhcp-multi.sh "$netif" "$DO_VLAN" "$@" & + return 0 +} + # Run dhclient do_dhcp() { # dhclient-script will mark the netif up and generate the online @@ -410,6 +439,9 @@ for p in $(getargs ip=); do case $autoopt in dhcp|on|any) do_dhcp -4 ;; + single-dhcp) + do_dhcp_parallel -4 + exit 0 ;; dhcp6) load_ipv6 do_dhcp -6 ;; diff --git a/modules.d/35network-legacy/module-setup.sh b/modules.d/35network-legacy/module-setup.sh index f7984c1a..0f44be3d 100755 --- a/modules.d/35network-legacy/module-setup.sh +++ b/modules.d/35network-legacy/module-setup.sh @@ -4,7 +4,7 @@ check() { local _program - require_binaries ip dhclient sed awk grep || return 1 + require_binaries ip dhclient sed awk grep pgrep || return 1 require_any_binary arping arping2 || return 1 return 255 @@ -23,7 +23,7 @@ installkernel() { # called by dracut install() { local _arch _i _dir - inst_multiple ip dhclient sed awk grep + inst_multiple ip dhclient sed awk grep pgrep inst_multiple -o arping arping2 strstr "$(arping 2>&1)" "ARPing 2" && mv "$initdir/bin/arping" "$initdir/bin/arping2" @@ -32,6 +32,7 @@ install() { inst_multiple -o teamd teamdctl teamnl inst_simple /etc/libnl/classid inst_script "$moddir/ifup.sh" "/sbin/ifup" + inst_script "$moddir/dhcp-multi.sh" "/sbin/dhcp-multi.sh" inst_script "$moddir/dhclient-script.sh" "/sbin/dhclient-script" inst_simple -H "/etc/dhclient.conf" cat "$moddir/dhclient.conf" >> "${initdir}/etc/dhclient.conf" diff --git a/modules.d/35network-legacy/parse-ip-opts.sh b/modules.d/35network-legacy/parse-ip-opts.sh index 10a2d19b..5a9f71f5 100755 --- a/modules.d/35network-legacy/parse-ip-opts.sh +++ b/modules.d/35network-legacy/parse-ip-opts.sh @@ -1,7 +1,7 @@ #!/bin/sh # # Format: -# ip=[dhcp|on|any] +# ip=[dhcp|on|any|single-dhcp] # # ip=:[dhcp|on|any][:[][:]] # @@ -77,7 +77,7 @@ for p in $(getargs ip=); do ;; auto6);; either6);; - dhcp|dhcp6|on|any) \ + dhcp|dhcp6|on|any|single-dhcp) \ [ -n "$NEEDBOOTDEV" ] && [ -z "$dev" ] && \ die "Sorry, 'ip=$p' does not make sense for multiple interface configurations" [ -n "$ip" ] && \ diff --git a/modules.d/40network/net-lib.sh b/modules.d/40network/net-lib.sh index 556d86b5..82724719 100755 --- a/modules.d/40network/net-lib.sh +++ b/modules.d/40network/net-lib.sh @@ -1,5 +1,7 @@ #!/bin/sh +IFNETFILE="/tmp/bootnetif" + is_ip() { echo "$1" | { IFS=. read a b c d @@ -460,7 +462,7 @@ ip_to_var() { fi if [ $# -eq 1 ]; then - # format: ip={dhcp|on|any|dhcp6|auto6|either6} + # format: ip={dhcp|on|any|dhcp6|auto6|either6|single-dhcp} # or # ip= means anaconda-style static config argument cluster autoconf="$1"