]> git.ipfire.org Git - thirdparty/dracut.git/commitdiff
feat(network-legacy): send dhcp in parallel on all devices
authorAnjali Kulkarni <anjali.k.kulkarni@oracle.com>
Thu, 4 Feb 2021 01:15:15 +0000 (17:15 -0800)
committerHarald Hoyer <harald@hoyer.xyz>
Fri, 5 Feb 2021 10:11:22 +0000 (11:11 +0100)
We can drastically improve boot times on some machines, or BM or VM
shapes, if we send DHCP requests in parallel on all interfaces
Add ip=single-dhcp as a kernel boot parameter, to send DHCP requests in
parallel on all interfaces. Thus, DHCP on each interface is overlapped
thereby reducing boot times for multiple interface machines.
This can help reduce the boot time by 1 minute per interface.
In addition, we may also save another 40 secs delay per interface, if
the link on which we try to send DHCP is in DOWN state. This can be
reduced by giving the following kernel command line parameter while
booting - rd.net.timeout.carrier=<timeout>.
Added manpage for ip=single-dhcp with the caveat that it does not
apply to Network Manager or SUSE wicked.

Signed-off-by: Anjali Kulkarni <anjali.k.kulkarni@oracle.com>
dracut.cmdline.7.asc
modules.d/35network-legacy/dhcp-multi.sh [new file with mode: 0755]
modules.d/35network-legacy/ifup.sh
modules.d/35network-legacy/module-setup.sh
modules.d/35network-legacy/parse-ip-opts.sh
modules.d/40network/net-lib.sh

index dc140d7f2af7969f1eab410d89c295b1bd268898..0951c0e0117d8eeb1c601e9d19f236cb93d29c67 100644 (file)
@@ -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 (executable)
index 0000000..8c58a69
--- /dev/null
@@ -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
index b1ae52ea5b260e607e943de0191940a95c5740f9..130b799f364d9025b4a8ba4e99b5b7f03b25037b 100755 (executable)
@@ -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 ;;
index f7984c1aa051f1d335e76817ec3b0928cf488563..0f44be3da5de2df3f6009d5a28cb2cb2c1455985 100755 (executable)
@@ -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"
index 10a2d19b4512eec054c1bafc3e30471677a1e1e8..5a9f71f549a80f90a39afae85210b4fde3c878a1 100755 (executable)
@@ -1,7 +1,7 @@
 #!/bin/sh
 #
 # Format:
-#       ip=[dhcp|on|any]
+#       ip=[dhcp|on|any|single-dhcp]
 #
 #       ip=<interface>:[dhcp|on|any][:[<mtu>][:<macaddr>]]
 #
@@ -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" ] && \
index 556d86b5301e350a6574fcdff47cbb8bfc056c69..8272471939059c5c4b5ab5e916af33279109883d 100755 (executable)
@@ -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=<ipv4-address> means anaconda-style static config argument cluster
         autoconf="$1"