On Thu, 2 Oct 2025, Dag-Erling Smørgrav wrote:

The branch main has been updated by des:

URL: 
https://cgit.FreeBSD.org/src/commit/?id=5ead817c3b7af6d6b5fea222ab144db2c3167b22

commit 5ead817c3b7af6d6b5fea222ab144db2c3167b22
Author:     Dag-Erling Smørgrav <[email protected]>
AuthorDate: 2025-10-02 09:28:05 +0000
Commit:     Dag-Erling Smørgrav <[email protected]>
CommitDate: 2025-10-02 09:31:16 +0000

   rc: Teach netwait to wait for DAD

   In some configurations, especially in jails, it is possible for the
   system to boot so fast that we end up launching daemons while duplicate
   address detection is still ongoing.  If that happens, said daemons may
   fail to bind to IPv6 addresses, as they are still tentative.  Teach the
   netwait service to wait (up to 10 seconds, by default) for the tentative
   flag to vanish.

Why 10s?

You should never have to wait longer than net.inet6.ip6.dad_count + 1 seconds.

Also the extra variable netwait_dad_timeout really isn't necessary in that case
if you use the sysctl.

/bz


   MFC after:      1 week
   Reviewed by:    olce
   Differential Revision:  https://reviews.freebsd.org/D51889
---
libexec/rc/rc.conf       |  2 ++
libexec/rc/rc.d/netwait  | 84 ++++++++++++++++++++++++++++++++++--------------
share/man/man5/rc.conf.5 | 24 +++++++++-----
3 files changed, 78 insertions(+), 32 deletions(-)

diff --git a/libexec/rc/rc.conf b/libexec/rc/rc.conf
index c354aec44430..2589e2614c35 100644
--- a/libexec/rc/rc.conf
+++ b/libexec/rc/rc.conf
@@ -500,6 +500,8 @@ netwait_enable="NO"               # Enable rc.d/netwait (or 
NO)
netwait_timeout="60"          # Total number of seconds to perform pings.
#netwait_if=""                        # Wait for active link on each intf in 
this list.
netwait_if_timeout="30"               # Total number of seconds to monitor link 
state.
+netwait_dad="NO"             # Wait for DAD to complete
+netwait_dad_timeout="10"     # Total number of seconds to wait for DAD.

### Miscellaneous network options: ###
icmp_bmcastecho="NO"  # respond to broadcast ping packets
diff --git a/libexec/rc/rc.d/netwait b/libexec/rc/rc.d/netwait
index 3f374806d97c..b609440a2e4e 100755
--- a/libexec/rc/rc.d/netwait
+++ b/libexec/rc/rc.d/netwait
@@ -2,12 +2,14 @@
#
# PROVIDE: netwait
# REQUIRE: devd ipfw pf routing
-# KEYWORD: nojail
#
-# The netwait script helps handle two situations:
+# The netwait script helps handle three situations:
#  - Systems with USB or other late-attaching network hardware which
#    is initialized by devd events.  The script waits for all the
#    interfaces named in the netwait_if list to appear.
+#  - Systems with IPv6 addresses, especially jails, where we need to
+#    wait for DAD to complete before starting daemons, as they will
+#    otherwise fail to bind to IN6ADDR_ANY.
#  - Systems with statically-configured IP addresses in rc.conf(5).
#    The IP addresses in the netwait_ip list are pinged.  The script
#    waits for any single IP in the list to respond to the ping.  If your
@@ -29,28 +31,36 @@ netwait_start()
{
        local ip rc count output link wait_if got_if any_error

-       if [ -z "${netwait_if}" ] && [ -z "${netwait_ip}" ]; then
-               err 1 "No interface or IP addresses listed, nothing to wait for"
+       if [ -z "${netwait_if}" ] && [ -z "${netwait_ip}" ] &&
+          ! checkyesno netwait_dad ; then
+               err 1 "Nothing to wait for"
        fi

-       if [ ${netwait_timeout} -lt 1 ]; then
+       if ! [ "${netwait_if_timeout}" -ge 1 ]; then
+               err 1 "netwait_if_timeout must be >= 1"
+       fi
+       if ! [ "${netwait_dad_timeout}" -ge 1 ]; then
+               err 1 "netwait_dad_timeout must be >= 1"
+       fi
+       if ! [ "${netwait_timeout}" -ge 1 ]; then
                err 1 "netwait_timeout must be >= 1"
        fi

+       any_error=false
+
        if [ -n "${netwait_if}" ]; then
-               any_error=0
                for wait_if in ${netwait_if}; do
                        echo -n "Waiting for ${wait_if}"
                        link=""
-                       got_if=0
+                       got_if=false
                        count=1
-                       # Handle SIGINT (Ctrl-C); force abort of while() loop
+                       # Handle SIGINT (Ctrl-C); force abort of while loop
                        trap break SIGINT
                        while [ ${count} -le ${netwait_if_timeout} ]; do
                                if output=`/sbin/ifconfig ${wait_if} 
2>/dev/null`; then
-                                       if [ ${got_if} -eq 0 ]; then
+                                       if ! ${got_if}; then
                                                echo -n ", interface present"
-                                               got_if=1
+                                               got_if=true
                                        fi
                                        link=`expr "${output}" : 
'.*[[:blank:]]status: \(no carrier\)'`
                                        if [ -z "${link}" ]; then
@@ -63,22 +73,45 @@ netwait_start()
                        done
                        # Restore default SIGINT handler
                        trap - SIGINT
-                       if [ ${got_if} -eq 0 ]; then
+                       if ! ${got_if}; then
                                echo ", wait failed: interface never appeared."
-                               any_error=1
+                               any_error=true
                        elif [ -n "${link}" ]; then
                                echo ", wait failed: interface still has no 
link."
-                               any_error=1
+                               any_error=true
                        fi
                done
-               if [ ${any_error} -eq 1 ]; then
-                   warn "Continuing with startup, but be aware you may not have 
"
-                   warn "a fully functional networking layer at this point."
-               fi
        fi

+       if checkyesno netwait_dad; then
+               got_dad=false
+               # Handle SIGINT (Ctrl-C); force abort of while loop
+               trap break SIGINT
+
+               echo -n "Waiting for DAD to complete"
+               count=1
+               while [ ${count} -le ${netwait_dad_timeout} ]; do
+                       if ! ifconfig | grep -q 'inet6.*tentative'; then
+                               echo ', done.'
+                               got_dad=true
+                               break
+                       fi
+                       sleep 1
+                       count=$((count+1))
+               done
+
+               # Restore default SIGINT handler
+               trap - SIGINT
+
+               if ! ${got_dad}; then
+                       echo ', timed out.'
+                       any_error=true
+               fi
+       fi
+
        if [ -n "${netwait_ip}" ]; then
-               # Handle SIGINT (Ctrl-C); force abort of for() loop
+               got_ip=false
+               # Handle SIGINT (Ctrl-C); force abort of for loop
                trap break SIGINT

                for ip in ${netwait_ip}; do
@@ -90,11 +123,9 @@ netwait_start()
                                rc=$?

                                if [ $rc -eq 0 ]; then
-                                       # Restore default SIGINT handler
-                                       trap - SIGINT
-
                                        echo ', got response.'
-                                       return
+                                       got_ip=false
+                                       break 2
                                fi
                                count=$((count+1))
                        done
@@ -104,10 +135,15 @@ netwait_start()
                # Restore default SIGINT handler
                trap - SIGINT

-               warn "Exhausted IP list.  Continuing with startup, but be aware you 
may"
-               warn "not have a fully functional networking layer at this 
point."
+               if ! ${got_ip}; then
+                       any_error=true
+               fi
        fi

+       if ${any_error}; then
+               warn "Continuing with startup, but be aware you may not have "
+               warn "a fully functional networking layer at this point."
+       fi
}

load_rc_config $name
diff --git a/share/man/man5/rc.conf.5 b/share/man/man5/rc.conf.5
index b8c72fc07083..c0048f27c740 100644
--- a/share/man/man5/rc.conf.5
+++ b/share/man/man5/rc.conf.5
@@ -22,7 +22,7 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
-.Dd September 25, 2025
+.Dd October 2, 2025
.Dt RC.CONF 5
.Os
.Sh NAME
@@ -4563,20 +4563,16 @@ If set to
.Dq Li YES ,
delays the start of network-reliant services until
.Va netwait_if
-is up and ICMP packets to a destination defined in
+is up, duplicate address discovery (DAD) has completed, and ICMP
+packets to a destination defined in
.Va netwait_ip
are flowing.
-Link state is examined first, followed by
+Link state is examined first, followed by DAD, then
.Dq Li pinging
an IP address to verify network usability.
If no destination can be reached or timeouts are exceeded,
network services are started anyway with no guarantee that
the network is usable.
-Use of this variable requires both
-.Va netwait_ip
-and
-.Va netwait_if
-to be set.
.It Va netwait_ip
.Pq Vt str
Empty by default.
@@ -4612,6 +4608,18 @@ interface if desired.
Defines the total number of seconds to wait for link to become usable,
polled at a 1-second interval.
The default is 30.
+.It Va netwait_dad
+.Pq Vt str
+Set to
+.Dq Li NO
+by default.
+Set to
+.Dq Li YES
+to enable waiting for DAD to complete.
+.It Va netwait_dad_timeout
+.Pq Vt int
+Indicates the total number of seconds to wait for DAD to complete.
+The default is 10.
.It Va rctl_enable
.Pq Vt bool
If set to


--
Bjoern A. Zeeb                                                     r15:7

Reply via email to