Re: git: 5ead817c3b7a - main - rc: Teach netwait to wait for DAD
Date: Sat, 04 Oct 2025 16:23:22 UTC
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 <des@FreeBSD.org>
> AuthorDate: 2025-10-02 09:28:05 +0000
> Commit: Dag-Erling Smørgrav <des@FreeBSD.org>
> 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