From 68b625901f9eb7c34e3d7aa302e1c0a454d3190b Mon Sep 17 00:00:00 2001
From: Amir Masoud Abdol <amir.abdol@qt.io>
Date: Wed, 1 Mar 2023 15:41:14 +0100
Subject: [PATCH] Network: link directly to libresolv instead of dlopen()ing it
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

There's little need for us to dynamically load it. The reasons why that
was necessary aren't in the public history (Qt 4.5 already had it[1]). I
remember writing the code in 2007-2008, I just don't remember why.

On modern Linux and FreeBSD, there's no libresolv.so any more and those
symbols have been rolled up into libc.so. It's still necessary on Darwin
systems, so this commit introduces WrapResolv.

It also resolves the unity build issues relating to libresolv symbols.

[1] https://code.qt.io/cgit/qt/qt.git/tree/src/network/kernel/qhostinfo_unix.cpp?h=v4.5.1

Task-number: QTBUG-109394
Change-Id: Ic5799e4d000b6c9395109e008780643bac52122b
Reviewed-by: MÃ¥rten Nordheim <marten.nordheim@qt.io>
---
 cmake/FindWrapResolv.cmake                    |  49 ++++
 src/network/CMakeLists.txt                    |  39 ++--
 src/network/configure.cmake                   |  26 ++-
 ...ookup_android.cpp => qdnslookup_dummy.cpp} |   2 +-
 src/network/kernel/qdnslookup_unix.cpp        | 144 ++----------
 src/network/kernel/qhostinfo_unix.cpp         | 216 ++++++------------
 tests/auto/network/kernel/CMakeLists.txt      |   2 +-
 .../kernel/qdnslookup/tst_qdnslookup.cpp      |  15 --
 .../kernel/qhostinfo/tst_qhostinfo.cpp        |   1 +
 9 files changed, 179 insertions(+), 315 deletions(-)
 create mode 100644 cmake/FindWrapResolv.cmake
 rename src/network/kernel/{qdnslookup_android.cpp => qdnslookup_dummy.cpp} (90%)

diff --git a/cmake/FindWrapResolv.cmake b/cmake/FindWrapResolv.cmake
new file mode 100644
index 0000000000..9720c96c00
--- /dev/null
+++ b/cmake/FindWrapResolv.cmake
@@ -0,0 +1,49 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# Copyright (C) 2023 Intel Corpotation.
+# SPDX-License-Identifier: BSD-3-Clause
+
+# We can't create the same interface imported target multiple times, CMake will complain if we do
+# that. This can happen if the find_package call is done in multiple different subdirectories.
+if(TARGET WrapResolv::WrapResolv)
+    set(WrapResolv_FOUND ON)
+    return()
+endif()
+
+set(WrapResolv_FOUND OFF)
+
+include(CheckCXXSourceCompiles)
+include(CMakePushCheckState)
+
+if(QNX)
+    find_library(LIBRESOLV socket)
+else()
+    find_library(LIBRESOLV resolv)
+endif()
+
+cmake_push_check_state()
+if(LIBRESOLV)
+    list(APPEND CMAKE_REQUIRED_LIBRARIES "${LIBRESOLV}")
+endif()
+
+check_cxx_source_compiles("
+#include <netinet/in.h>
+#include <resolv.h>
+
+int main(int, char **)
+{
+    res_init();
+}
+" HAVE_RES_INIT)
+
+cmake_pop_check_state()
+
+if(HAVE_RES_INIT)
+    set(WrapResolv_FOUND ON)
+    add_library(WrapResolv::WrapResolv INTERFACE IMPORTED)
+    if(LIBRESOLV)
+        target_link_libraries(WrapResolv::WrapResolv INTERFACE "${LIBRESOLV}")
+    endif()
+endif()
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(WrapResolv DEFAULT_MSG WrapResolv_FOUND)
diff --git a/src/network/CMakeLists.txt b/src/network/CMakeLists.txt
index 827fc0648b..ac0e45fb9e 100644
--- a/src/network/CMakeLists.txt
+++ b/src/network/CMakeLists.txt
@@ -174,15 +174,6 @@ qt_internal_extend_target(Network CONDITION UNIX
         kernel/qhostinfo_unix.cpp
         socket/qnativesocketengine_unix.cpp
         socket/qnet_unix_p.h
-    NO_UNITY_BUILD_SOURCES
-        kernel/qhostinfo_unix.cpp # Temporary exclusion until, this,
-                                  # https://codereview.qt-project.org/c/qt/qtbase/+/463670
-                                  # is merged.
-)
-
-qt_internal_extend_target(Network CONDITION QT_FEATURE_dlopen AND UNIX
-    LIBRARIES
-        ${CMAKE_DL_LIBS}
 )
 
 qt_internal_extend_target(Network CONDITION QT_FEATURE_linux_netlink AND UNIX
@@ -195,11 +186,6 @@ qt_internal_extend_target(Network CONDITION UNIX AND NOT QT_FEATURE_linux_netlin
         kernel/qnetworkinterface_unix.cpp
 )
 
-qt_internal_extend_target(Network CONDITION ANDROID AND QT_FEATURE_dnslookup
-    SOURCES
-        kernel/qdnslookup_android.cpp
-)
-
 qt_internal_extend_target(Network CONDITION WIN32
     SOURCES
         kernel/qhostinfo_win.cpp
@@ -214,11 +200,6 @@ qt_internal_extend_target(Network CONDITION WIN32
         winhttp
 )
 
-qt_internal_extend_target(Network CONDITION QT_FEATURE_dnslookup AND WIN32
-    SOURCES
-        kernel/qdnslookup_win.cpp
-)
-
 qt_internal_extend_target(Network CONDITION APPLE AND NOT UIKIT
     LIBRARIES
         ${FWCoreServices}
@@ -236,6 +217,22 @@ qt_internal_extend_target(Network CONDITION APPLE
         ${FWCFNetwork}
 )
 
+qt_internal_extend_target(Network CONDITION QT_FEATURE_dnslookup AND TEST_res_ninit
+    SOURCES
+        kernel/qdnslookup_unix.cpp
+    LIBRARIES
+        WrapResolv::WrapResolv
+)
+
+qt_internal_extend_target(Network CONDITION QT_FEATURE_dnslookup AND WIN32
+    SOURCES
+        kernel/qdnslookup_win.cpp
+)
+
+qt_internal_extend_target(Network CONDITION QT_FEATURE_dnslookup AND NOT TEST_res_ninit AND NOT WIN32
+    SOURCES
+        kernel/qdnslookup_dummy.cpp
+)
 
 qt_internal_extend_target(Network CONDITION IOS OR MACOS
     SOURCES
@@ -369,10 +366,6 @@ qt_internal_extend_target(Network CONDITION QT_FEATURE_ocsp AND QT_FEATURE_opens
         ssl/qocsp_p.h
 )
 
-qt_internal_extend_target(Network CONDITION QT_FEATURE_dnslookup AND UNIX AND NOT ANDROID
-    SOURCES
-        kernel/qdnslookup_unix.cpp
-)
 qt_internal_add_docs(Network
     doc/qtnetwork.qdocconf
 )
diff --git a/src/network/configure.cmake b/src/network/configure.cmake
index 32603dff95..0fd3a103b2 100644
--- a/src/network/configure.cmake
+++ b/src/network/configure.cmake
@@ -12,7 +12,7 @@ qt_find_package(Libproxy PROVIDED_TARGETS PkgConfig::Libproxy MODULE_NAME networ
 qt_find_package(GSSAPI PROVIDED_TARGETS GSSAPI::GSSAPI MODULE_NAME network QMAKE_LIB gssapi)
 qt_find_package(GLIB2 OPTIONAL_COMPONENTS GOBJECT PROVIDED_TARGETS GLIB2::GOBJECT MODULE_NAME core QMAKE_LIB gobject)
 qt_find_package(GLIB2 OPTIONAL_COMPONENTS GIO PROVIDED_TARGETS GLIB2::GIO MODULE_NAME core QMAKE_LIB gio)
-
+qt_find_package(WrapResolv PROVIDED_TARGETS WrapResolv::WrapResolv MODULE_NAME network QMAKE_LIB libresolv)
 
 #### Tests
 
@@ -100,6 +100,25 @@ ci.ifa_prefered = ci.ifa_valid = 0;
 }
 ")
 
+# res_ninit
+qt_config_compile_test(res_ninit
+    LABEL "res_ninit()"
+    LIBRARIES
+        WrapResolv::WrapResolv
+    CODE
+"#include <sys/types.h>
+#include <netinet/in.h>
+#include <resolv.h>
+int main()
+{
+    res_state state;
+    res_ninit(state);
+    res_nclose(state);
+    return 0;
+}
+"
+)
+
 # sctp
 qt_config_compile_test(sctp
     LABEL "SCTP support"
@@ -211,6 +230,11 @@ qt_feature("linux-netlink" PRIVATE
     LABEL "Linux AF_NETLINK"
     CONDITION LINUX AND NOT ANDROID AND TEST_linux_netlink
 )
+qt_feature("res_ninit" PRIVATE
+    LABEL "res_ninit()"
+    CONDITION TEST_res_ninit
+    AUTODETECT UNIX
+)
 qt_feature("securetransport" PUBLIC
     LABEL "SecureTransport"
     CONDITION APPLE
diff --git a/src/network/kernel/qdnslookup_android.cpp b/src/network/kernel/qdnslookup_dummy.cpp
similarity index 90%
rename from src/network/kernel/qdnslookup_android.cpp
rename to src/network/kernel/qdnslookup_dummy.cpp
index 8fc1265e80..d17cc66dce 100644
--- a/src/network/kernel/qdnslookup_android.cpp
+++ b/src/network/kernel/qdnslookup_dummy.cpp
@@ -13,7 +13,7 @@ void QDnsLookupRunnable::query(const int requestType, const QByteArray &requestN
     Q_UNUSED(reply);
     qWarning("Not yet supported on Android");
     reply->error = QDnsLookup::ResolverError;
-    reply->errorString = tr("Not yet supported on Android");
+    reply->errorString = tr("Not yet supported on this OS");
     return;
 }
 
diff --git a/src/network/kernel/qdnslookup_unix.cpp b/src/network/kernel/qdnslookup_unix.cpp
index 75f7c6c440..05e0663aef 100644
--- a/src/network/kernel/qdnslookup_unix.cpp
+++ b/src/network/kernel/qdnslookup_unix.cpp
@@ -3,130 +3,36 @@
 
 #include "qdnslookup_p.h"
 
-#if QT_CONFIG(library)
-#include <qlibrary.h>
-#endif
-#include <qvarlengtharray.h>
 #include <qscopedpointer.h>
 #include <qurl.h>
-#include <private/qnativesocketengine_p.h>
+#include <qvarlengtharray.h>
+#include <private/qnativesocketengine_p.h>      // for SetSALen
+#include <private/qtnetwork-config_p.h>
+
+QT_REQUIRE_CONFIG(res_ninit);
 
 #include <sys/types.h>
 #include <netinet/in.h>
 #include <arpa/nameser.h>
-#if !defined(Q_OS_OPENBSD)
+#if __has_include(<arpa/nameser_compat.h>)
 #  include <arpa/nameser_compat.h>
 #endif
 #include <resolv.h>
 
-#if defined(__GNU_LIBRARY__) && !defined(__UCLIBC__)
-#  include <gnu/lib-names.h>
-#endif
-
-#if defined(Q_OS_FREEBSD) || QT_CONFIG(dlopen)
-#  include <dlfcn.h>
-#endif
-
-#include <cstring>
-
 QT_BEGIN_NAMESPACE
 
 using namespace Qt::StringLiterals;
 
-#if QT_CONFIG(library)
-
-#if defined(Q_OS_OPENBSD)
-typedef struct __res_state* res_state;
-#endif
-typedef int (*dn_expand_proto)(const unsigned char *, const unsigned char *, const unsigned char *, char *, int);
-static dn_expand_proto local_dn_expand = nullptr;
-typedef void (*res_nclose_proto)(res_state);
-static res_nclose_proto local_res_nclose = nullptr;
-typedef int (*res_ninit_proto)(res_state);
-static res_ninit_proto local_res_ninit = nullptr;
-typedef int (*res_nquery_proto)(res_state, const char *, int, int, unsigned char *, int);
-static res_nquery_proto local_res_nquery = nullptr;
-
-// Custom deleter to close resolver state.
-
-struct QDnsLookupStateDeleter
-{
-    static inline void cleanup(struct __res_state *pointer)
-    {
-        local_res_nclose(pointer);
-    }
-};
-
-static QFunctionPointer resolveSymbol(QLibrary &lib, const char *sym)
-{
-    if (lib.isLoaded())
-        return lib.resolve(sym);
-
-#if defined(RTLD_DEFAULT) && (defined(Q_OS_FREEBSD) || QT_CONFIG(dlopen))
-    return reinterpret_cast<QFunctionPointer>(dlsym(RTLD_DEFAULT, sym));
-#else
-    return nullptr;
-#endif
-}
-
-static bool resolveLibraryInternal()
-{
-    QLibrary lib;
-#ifdef LIBRESOLV_SO
-    lib.setFileName(QStringLiteral(LIBRESOLV_SO));
-    if (!lib.load())
-#endif
-    {
-        lib.setFileName("resolv"_L1);
-        lib.load();
-    }
-
-    local_dn_expand = dn_expand_proto(resolveSymbol(lib, "__dn_expand"));
-    if (!local_dn_expand)
-        local_dn_expand = dn_expand_proto(resolveSymbol(lib, "dn_expand"));
-
-    local_res_nclose = res_nclose_proto(resolveSymbol(lib, "__res_nclose"));
-    if (!local_res_nclose)
-        local_res_nclose = res_nclose_proto(resolveSymbol(lib, "res_9_nclose"));
-    if (!local_res_nclose)
-        local_res_nclose = res_nclose_proto(resolveSymbol(lib, "res_nclose"));
-
-    local_res_ninit = res_ninit_proto(resolveSymbol(lib, "__res_ninit"));
-    if (!local_res_ninit)
-        local_res_ninit = res_ninit_proto(resolveSymbol(lib, "res_9_ninit"));
-    if (!local_res_ninit)
-        local_res_ninit = res_ninit_proto(resolveSymbol(lib, "res_ninit"));
-
-    local_res_nquery = res_nquery_proto(resolveSymbol(lib, "__res_nquery"));
-    if (!local_res_nquery)
-        local_res_nquery = res_nquery_proto(resolveSymbol(lib, "res_9_nquery"));
-    if (!local_res_nquery)
-        local_res_nquery = res_nquery_proto(resolveSymbol(lib, "res_nquery"));
-
-    return true;
-}
-Q_GLOBAL_STATIC_WITH_ARGS(bool, resolveLibrary, (resolveLibraryInternal()))
-
 void QDnsLookupRunnable::query(const int requestType, const QByteArray &requestName, const QHostAddress &nameserver, QDnsLookupReply *reply)
 {
-    // Load dn_expand, res_ninit and res_nquery on demand.
-    resolveLibrary();
-
-    // If dn_expand, res_ninit or res_nquery is missing, fail.
-    if (!local_dn_expand || !local_res_nclose || !local_res_ninit || !local_res_nquery) {
-        reply->error = QDnsLookup::ResolverError;
-        reply->errorString = tr("Resolver functions not found");
-        return;
-    }
-
     // Initialize state.
-    struct __res_state state;
-    std::memset(&state, 0, sizeof(state));
-    if (local_res_ninit(&state) < 0) {
+    std::remove_pointer_t<res_state> state = {};
+    if (res_ninit(&state) < 0) {
         reply->error = QDnsLookup::ResolverError;
         reply->errorString = tr("Resolver initialization failed");
         return;
     }
+    auto guard = qScopeGuard([&] { res_nclose(&state); });
 
     //Check if a nameserver was set. If so, use it
     if (!nameserver.isNull()) {
@@ -170,16 +76,15 @@ void QDnsLookupRunnable::query(const int requestType, const QByteArray &requestN
 #ifdef QDNSLOOKUP_DEBUG
     state.options |= RES_DEBUG;
 #endif
-    QScopedPointer<struct __res_state, QDnsLookupStateDeleter> state_ptr(&state);
 
     // Perform DNS query.
     QVarLengthArray<unsigned char, PACKETSZ> buffer(PACKETSZ);
     std::memset(buffer.data(), 0, buffer.size());
-    int responseLength = local_res_nquery(&state, requestName, C_IN, requestType, buffer.data(), buffer.size());
+    int responseLength = res_nquery(&state, requestName, C_IN, requestType, buffer.data(), buffer.size());
     if (Q_UNLIKELY(responseLength > PACKETSZ)) {
         buffer.resize(responseLength);
         std::memset(buffer.data(), 0, buffer.size());
-        responseLength = local_res_nquery(&state, requestName, C_IN, requestType, buffer.data(), buffer.size());
+        responseLength = res_nquery(&state, requestName, C_IN, requestType, buffer.data(), buffer.size());
         if (Q_UNLIKELY(responseLength > buffer.size())) {
             // Ok, we give up.
             reply->error = QDnsLookup::ResolverError;
@@ -229,7 +134,7 @@ void QDnsLookupRunnable::query(const int requestType, const QByteArray &requestN
     // Skip the query host, type (2 bytes) and class (2 bytes).
     char host[PACKETSZ], answer[PACKETSZ];
     unsigned char *p = response + sizeof(HEADER);
-    int status = local_dn_expand(response, response + responseLength, p, host, sizeof(host));
+    int status = dn_expand(response, response + responseLength, p, host, sizeof(host));
     if (status < 0) {
         reply->error = QDnsLookup::InvalidReplyError;
         reply->errorString = tr("Could not expand domain name");
@@ -240,7 +145,7 @@ void QDnsLookupRunnable::query(const int requestType, const QByteArray &requestN
     // Extract results.
     int answerIndex = 0;
     while ((p < response + responseLength) && (answerIndex < answerCount)) {
-        status = local_dn_expand(response, response + responseLength, p, host, sizeof(host));
+        status = dn_expand(response, response + responseLength, p, host, sizeof(host));
         if (status < 0) {
             reply->error = QDnsLookup::InvalidReplyError;
             reply->errorString = tr("Could not expand domain name");
@@ -281,7 +186,7 @@ void QDnsLookupRunnable::query(const int requestType, const QByteArray &requestN
             record.d->value = QHostAddress(p);
             reply->hostAddressRecords.append(record);
         } else if (type == QDnsLookup::CNAME) {
-            status = local_dn_expand(response, response + responseLength, p, answer, sizeof(answer));
+            status = dn_expand(response, response + responseLength, p, answer, sizeof(answer));
             if (status < 0) {
                 reply->error = QDnsLookup::InvalidReplyError;
                 reply->errorString = tr("Invalid canonical name record");
@@ -293,7 +198,7 @@ void QDnsLookupRunnable::query(const int requestType, const QByteArray &requestN
             record.d->value = QUrl::fromAce(answer);
             reply->canonicalNameRecords.append(record);
         } else if (type == QDnsLookup::NS) {
-            status = local_dn_expand(response, response + responseLength, p, answer, sizeof(answer));
+            status = dn_expand(response, response + responseLength, p, answer, sizeof(answer));
             if (status < 0) {
                 reply->error = QDnsLookup::InvalidReplyError;
                 reply->errorString = tr("Invalid name server record");
@@ -305,7 +210,7 @@ void QDnsLookupRunnable::query(const int requestType, const QByteArray &requestN
             record.d->value = QUrl::fromAce(answer);
             reply->nameServerRecords.append(record);
         } else if (type == QDnsLookup::PTR) {
-            status = local_dn_expand(response, response + responseLength, p, answer, sizeof(answer));
+            status = dn_expand(response, response + responseLength, p, answer, sizeof(answer));
             if (status < 0) {
                 reply->error = QDnsLookup::InvalidReplyError;
                 reply->errorString = tr("Invalid pointer record");
@@ -318,7 +223,7 @@ void QDnsLookupRunnable::query(const int requestType, const QByteArray &requestN
             reply->pointerRecords.append(record);
         } else if (type == QDnsLookup::MX) {
             const quint16 preference = (p[0] << 8) | p[1];
-            status = local_dn_expand(response, response + responseLength, p + 2, answer, sizeof(answer));
+            status = dn_expand(response, response + responseLength, p + 2, answer, sizeof(answer));
             if (status < 0) {
                 reply->error = QDnsLookup::InvalidReplyError;
                 reply->errorString = tr("Invalid mail exchange record");
@@ -334,7 +239,7 @@ void QDnsLookupRunnable::query(const int requestType, const QByteArray &requestN
             const quint16 priority = (p[0] << 8) | p[1];
             const quint16 weight = (p[2] << 8) | p[3];
             const quint16 port = (p[4] << 8) | p[5];
-            status = local_dn_expand(response, response + responseLength, p + 6, answer, sizeof(answer));
+            status = dn_expand(response, response + responseLength, p + 6, answer, sizeof(answer));
             if (status < 0) {
                 reply->error = QDnsLookup::InvalidReplyError;
                 reply->errorString = tr("Invalid service record");
@@ -371,17 +276,4 @@ void QDnsLookupRunnable::query(const int requestType, const QByteArray &requestN
     }
 }
 
-#else
-void QDnsLookupRunnable::query(const int requestType, const QByteArray &requestName, const QHostAddress &nameserver, QDnsLookupReply *reply)
-{
-    Q_UNUSED(requestType);
-    Q_UNUSED(requestName);
-    Q_UNUSED(nameserver);
-    reply->error = QDnsLookup::ResolverError;
-    reply->errorString = tr("Resolver library can't be loaded: No runtime library loading support");
-    return;
-}
-
-#endif /* QT_CONFIG(library) */
-
 QT_END_NAMESPACE
diff --git a/src/network/kernel/qhostinfo_unix.cpp b/src/network/kernel/qhostinfo_unix.cpp
index 12d8c04d10..331c5fbf45 100644
--- a/src/network/kernel/qhostinfo_unix.cpp
+++ b/src/network/kernel/qhostinfo_unix.cpp
@@ -3,140 +3,75 @@
 
 //#define QHOSTINFO_DEBUG
 
-#include "qplatformdefs.h"
-
 #include "qhostinfo_p.h"
-#include "private/qnativesocketengine_p.h"
-#include "qiodevice.h"
-#include <qbytearray.h>
-#if QT_CONFIG(library)
-#include <qlibrary.h>
-#endif
-#include <qbasicatomic.h>
-#include <qurl.h>
-#include <qfile.h>
-#include <private/qnet_unix_p.h>
 
-#include "QtCore/qapplicationstatic.h"
+#include <qbytearray.h>
+#include <qfile.h>
+#include <qplatformdefs.h>
+#include <qurl.h>
 
 #include <sys/types.h>
 #include <netdb.h>
-#include <arpa/inet.h>
-#if defined(Q_OS_VXWORKS)
-#  include <hostLib.h>
-#else
-#  include <resolv.h>
-#endif
+#include <netinet/in.h>
+#include <resolv.h>
 
-#if defined(__GNU_LIBRARY__) && !defined(__UCLIBC__)
-#  include <gnu/lib-names.h>
-#endif
-
-#if defined(Q_OS_FREEBSD) || QT_CONFIG(dlopen)
-#  include <dlfcn.h>
+#ifndef _PATH_RESCONF
+#  define _PATH_RESCONF "/etc/resolv.conf"
 #endif
 
 QT_BEGIN_NAMESPACE
 
 using namespace Qt::StringLiterals;
 
-enum LibResolvFeature {
-    NeedResInit,
-    NeedResNInit
-};
-
-typedef struct __res_state *res_state_ptr;
-
-typedef int (*res_init_proto)(void);
-static res_init_proto local_res_init = nullptr;
-typedef int (*res_ninit_proto)(res_state_ptr);
-static res_ninit_proto local_res_ninit = nullptr;
-typedef void (*res_nclose_proto)(res_state_ptr);
-static res_nclose_proto local_res_nclose = nullptr;
-static res_state_ptr local_res = nullptr;
-
-#if QT_CONFIG(library) && !defined(Q_OS_QNX)
-namespace {
-struct LibResolv
+static void maybeRefreshResolver()
 {
-    enum {
-#ifdef RES_NORELOAD
-        // If RES_NORELOAD is defined, then the libc is capable of watching
-        // /etc/resolv.conf for changes and reloading as necessary. So accept
-        // whatever is configured.
-        ReinitNecessary = false
-#else
-        ReinitNecessary = true
+#if defined(RES_NORELOAD)
+    // If RES_NORELOAD is defined, then the libc is capable of watching
+    // /etc/resolv.conf for changes and reloading as necessary. So accept
+    // whatever is configured.
+    return;
+#elif defined(Q_OS_DARWIN)
+    // Apple's libsystem_info.dylib:getaddrinfo() uses the
+    // libsystem_dnssd.dylib to resolve hostnames. Using res_init() has no
+    // effect on it and is thread-unsafe.
+    return;
+#elif defined(Q_OS_FREEBSD)
+    // FreeBSD automatically refreshes:
+    // https://github.com/freebsd/freebsd-src/blob/b3fe5d932264445cbf9a1c4eab01afb6179b499b/lib/libc/resolv/res_state.c#L69
+    return;
+#elif defined(Q_OS_OPENBSD)
+    // OpenBSD automatically refreshes:
+    // https://github.com/ligurio/openbsd-src/blob/b1ce0da17da254cc15b8aff25b3d55d3c7a82cec/lib/libc/asr/asr.c#L367
+    return;
+#elif defined(Q_OS_QNX)
+    // res_init() is not thread-safe; executing it leads to state corruption.
+    // Whether it reloads resolv.conf on its own is unknown.
+    return;
 #endif
-    };
 
-    QLibrary lib;
-    LibResolv();
-    ~LibResolv() { lib.unload(); }
-};
-}
+#if QT_CONFIG(res_ninit)
+    // OSes known or thought to reach here: AIX, NetBSD, Solaris,
+    // Linux with MUSL (though res_init() does nothing and is unnecessary)
 
-static QFunctionPointer resolveSymbol(QLibrary &lib, const char *sym)
-{
-    if (lib.isLoaded())
-        return lib.resolve(sym);
-
-#if defined(RTLD_DEFAULT) && (defined(Q_OS_FREEBSD) || QT_CONFIG(dlopen))
-    return reinterpret_cast<QFunctionPointer>(dlsym(RTLD_DEFAULT, sym));
-#else
-    return nullptr;
-#endif
-}
-
-LibResolv::LibResolv()
-{
-#ifdef LIBRESOLV_SO
-    lib.setFileName(QStringLiteral(LIBRESOLV_SO));
-    if (!lib.load())
-#endif
-    {
-        lib.setFileName("resolv"_L1);
-        lib.load();
-    }
-
-    // res_ninit is required for localDomainName()
-    local_res_ninit = res_ninit_proto(resolveSymbol(lib, "__res_ninit"));
-    if (!local_res_ninit)
-        local_res_ninit = res_ninit_proto(resolveSymbol(lib, "res_ninit"));
-    if (local_res_ninit) {
-        // we must now find res_nclose
-        local_res_nclose = res_nclose_proto(resolveSymbol(lib, "res_nclose"));
-        if (!local_res_nclose)
-            local_res_nclose = res_nclose_proto(resolveSymbol(lib, "__res_nclose"));
-        if (!local_res_nclose)
-            local_res_ninit = nullptr;
-    }
-
-    if (ReinitNecessary || !local_res_ninit) {
-        local_res_init = res_init_proto(resolveSymbol(lib, "__res_init"));
-        if (!local_res_init)
-            local_res_init = res_init_proto(resolveSymbol(lib, "res_init"));
-
-        if (local_res_init && !local_res_ninit) {
-            // if we can't get a thread-safe context, we have to use the global _res state
-            local_res = res_state_ptr(resolveSymbol(lib, "_res"));
+    Q_CONSTINIT static QT_STATBUF lastStat = {};
+    Q_CONSTINIT static QBasicMutex mutex = {};
+    if (QT_STATBUF st; QT_STAT(_PATH_RESCONF, &st) == 0) {
+        QMutexLocker locker(&mutex);
+        bool refresh = false;
+        if ((_res.options & RES_INIT) == 0)
+            refresh = true;
+        else if (lastStat.st_ctime != st.st_ctime)
+            refresh = true;     // file was updated
+        else if (lastStat.st_dev != st.st_dev || lastStat.st_ino != st.st_ino)
+            refresh = true;     // file was replaced
+        if (refresh) {
+            lastStat = st;
+            res_init();
         }
     }
+#endif
 }
 
-Q_APPLICATION_STATIC(LibResolv, libResolv)
-
-static void resolveLibrary(LibResolvFeature f)
-{
-    if (LibResolv::ReinitNecessary || f == NeedResNInit)
-        libResolv();
-}
-#else // QT_CONFIG(library) || Q_OS_QNX
-static void resolveLibrary(LibResolvFeature)
-{
-}
-#endif // QT_CONFIG(library) || Q_OS_QNX
-
 QHostInfo QHostInfoAgent::fromName(const QString &hostName)
 {
     QHostInfo results;
@@ -146,12 +81,7 @@ QHostInfo QHostInfoAgent::fromName(const QString &hostName)
            hostName.toLatin1().constData());
 #endif
 
-    // Load res_init on demand.
-    resolveLibrary(NeedResInit);
-
-    // If res_init is available, poll it.
-    if (local_res_init)
-        local_res_init();
+    maybeRefreshResolver();
 
     QHostAddress address;
     if (address.setAddress(hostName))
@@ -162,40 +92,30 @@ QHostInfo QHostInfoAgent::fromName(const QString &hostName)
 
 QString QHostInfo::localDomainName()
 {
-#if !defined(Q_OS_VXWORKS) && !defined(Q_OS_ANDROID)
-    resolveLibrary(NeedResNInit);
-    if (local_res_ninit) {
+#if QT_CONFIG(res_ninit)
+    auto domainNameFromRes = [](res_state r) {
+        QString domainName;
+        if (r->defdname[0])
+            domainName = QUrl::fromAce(r->defdname);
+        if (domainName.isEmpty())
+            domainName = QUrl::fromAce(r->dnsrch[0]);
+        return domainName;
+    };
+    std::remove_pointer_t<res_state> state = {};
+    if (res_ninit(&state) == 0) {
         // using thread-safe version
-        res_state_ptr state = res_state_ptr(malloc(sizeof(*state)));
-        Q_CHECK_PTR(state);
-        memset(state, 0, sizeof(*state));
-        local_res_ninit(state);
-        QString domainName = QUrl::fromAce(state->defdname);
-        if (domainName.isEmpty())
-            domainName = QUrl::fromAce(state->dnsrch[0]);
-        local_res_nclose(state);
-        free(state);
-
-        return domainName;
+        auto guard = qScopeGuard([&] { res_nclose(&state); });
+        return domainNameFromRes(&state);
     }
 
-    if (local_res_init && local_res) {
-        // using thread-unsafe version
+    // using thread-unsafe version
+    maybeRefreshResolver();
+    return domainNameFromRes(&_res);
+#endif  // !QT_CONFIG(res_ninit)
 
-        local_res_init();
-        QString domainName = QUrl::fromAce(local_res->defdname);
-        if (domainName.isEmpty())
-            domainName = QUrl::fromAce(local_res->dnsrch[0]);
-        return domainName;
-    }
-#endif
     // nothing worked, try doing it by ourselves:
     QFile resolvconf;
-#if defined(_PATH_RESCONF)
-    resolvconf.setFileName(QFile::decodeName(_PATH_RESCONF));
-#else
-    resolvconf.setFileName("/etc/resolv.conf"_L1);
-#endif
+    resolvconf.setFileName(_PATH_RESCONF ""_L1);
     if (!resolvconf.open(QIODevice::ReadOnly))
         return QString();       // failure
 
diff --git a/tests/auto/network/kernel/CMakeLists.txt b/tests/auto/network/kernel/CMakeLists.txt
index 2f5f935f29..f5760c9b0b 100644
--- a/tests/auto/network/kernel/CMakeLists.txt
+++ b/tests/auto/network/kernel/CMakeLists.txt
@@ -1,7 +1,7 @@
 # Copyright (C) 2022 The Qt Company Ltd.
 # SPDX-License-Identifier: BSD-3-Clause
 
-if(NOT INTEGRITY)
+if(WIN32 OR TEST_res_ninit)
     add_subdirectory(qdnslookup)
     add_subdirectory(qdnslookup_appless)
 endif()
diff --git a/tests/auto/network/kernel/qdnslookup/tst_qdnslookup.cpp b/tests/auto/network/kernel/qdnslookup/tst_qdnslookup.cpp
index d22d858b26..764fe9aee4 100644
--- a/tests/auto/network/kernel/qdnslookup/tst_qdnslookup.cpp
+++ b/tests/auto/network/kernel/qdnslookup/tst_qdnslookup.cpp
@@ -202,11 +202,6 @@ void tst_QDnsLookup::lookup()
     lookup.lookup();
     QTRY_VERIFY_WITH_TIMEOUT(lookup.isFinished(), Timeout);
 
-#if defined(Q_OS_ANDROID)
-    if (lookup.errorString() == QStringLiteral("Not yet supported on Android"))
-        QEXPECT_FAIL("", "Not yet supported on Android", Abort);
-#endif
-
     QVERIFY2(int(lookup.error()) == error,
              msgDnsLookup(lookup.error(), error, domain, cname, host, srv, mx, ns, ptr, lookup.errorString()));
     if (error == QDnsLookup::NoError)
@@ -301,11 +296,6 @@ void tst_QDnsLookup::lookupReuse()
     lookup.lookup();
     QTRY_VERIFY_WITH_TIMEOUT(lookup.isFinished(), Timeout);
 
-#if defined(Q_OS_ANDROID)
-    if (lookup.errorString() == QStringLiteral("Not yet supported on Android"))
-        QEXPECT_FAIL("", "Not yet supported on Android", Abort);
-#endif
-
     QCOMPARE(int(lookup.error()), int(QDnsLookup::NoError));
     QVERIFY(!lookup.hostAddressRecords().isEmpty());
     QCOMPARE(lookup.hostAddressRecords().first().name(), domainName("a-single"));
@@ -342,11 +332,6 @@ void tst_QDnsLookup::lookupAbortRetry()
     lookup.lookup();
     QTRY_VERIFY_WITH_TIMEOUT(lookup.isFinished(), Timeout);
 
-#if defined(Q_OS_ANDROID)
-    if (lookup.errorString() == QStringLiteral("Not yet supported on Android"))
-        QEXPECT_FAIL("", "Not yet supported on Android", Abort);
-#endif
-
     QCOMPARE(int(lookup.error()), int(QDnsLookup::NoError));
     QVERIFY(!lookup.hostAddressRecords().isEmpty());
     QCOMPARE(lookup.hostAddressRecords().first().name(), domainName("aaaa-single"));
diff --git a/tests/auto/network/kernel/qhostinfo/tst_qhostinfo.cpp b/tests/auto/network/kernel/qhostinfo/tst_qhostinfo.cpp
index 4892534362..273bf3a035 100644
--- a/tests/auto/network/kernel/qhostinfo/tst_qhostinfo.cpp
+++ b/tests/auto/network/kernel/qhostinfo/tst_qhostinfo.cpp
@@ -506,6 +506,7 @@ protected:
     inline void run() override
     {
          QHostInfo info = QHostInfo::fromName("a-single" TEST_DOMAIN);
+         QCOMPARE(info.errorString(), "Unknown error"); // no error
          QCOMPARE(info.error(), QHostInfo::NoError);
          QVERIFY(info.addresses().size() > 0);
          QCOMPARE(info.addresses().at(0).toString(), QString("192.0.2.1"));