Network: link directly to libresolv instead of dlopen()ing it

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>
This commit is contained in:
Amir Masoud Abdol 2023-03-01 15:41:14 +01:00 committed by Thiago Macieira
parent ff9da1db0b
commit 68b625901f
9 changed files with 179 additions and 315 deletions

View File

@ -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)

View File

@ -174,15 +174,6 @@ qt_internal_extend_target(Network CONDITION UNIX
kernel/qhostinfo_unix.cpp kernel/qhostinfo_unix.cpp
socket/qnativesocketengine_unix.cpp socket/qnativesocketengine_unix.cpp
socket/qnet_unix_p.h 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 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 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 qt_internal_extend_target(Network CONDITION WIN32
SOURCES SOURCES
kernel/qhostinfo_win.cpp kernel/qhostinfo_win.cpp
@ -214,11 +200,6 @@ qt_internal_extend_target(Network CONDITION WIN32
winhttp 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 qt_internal_extend_target(Network CONDITION APPLE AND NOT UIKIT
LIBRARIES LIBRARIES
${FWCoreServices} ${FWCoreServices}
@ -236,6 +217,22 @@ qt_internal_extend_target(Network CONDITION APPLE
${FWCFNetwork} ${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 qt_internal_extend_target(Network CONDITION IOS OR MACOS
SOURCES SOURCES
@ -369,10 +366,6 @@ qt_internal_extend_target(Network CONDITION QT_FEATURE_ocsp AND QT_FEATURE_opens
ssl/qocsp_p.h 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 qt_internal_add_docs(Network
doc/qtnetwork.qdocconf doc/qtnetwork.qdocconf
) )

View File

@ -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(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 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(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 #### 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 # sctp
qt_config_compile_test(sctp qt_config_compile_test(sctp
LABEL "SCTP support" LABEL "SCTP support"
@ -211,6 +230,11 @@ qt_feature("linux-netlink" PRIVATE
LABEL "Linux AF_NETLINK" LABEL "Linux AF_NETLINK"
CONDITION LINUX AND NOT ANDROID AND TEST_linux_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 qt_feature("securetransport" PUBLIC
LABEL "SecureTransport" LABEL "SecureTransport"
CONDITION APPLE CONDITION APPLE

View File

@ -13,7 +13,7 @@ void QDnsLookupRunnable::query(const int requestType, const QByteArray &requestN
Q_UNUSED(reply); Q_UNUSED(reply);
qWarning("Not yet supported on Android"); qWarning("Not yet supported on Android");
reply->error = QDnsLookup::ResolverError; reply->error = QDnsLookup::ResolverError;
reply->errorString = tr("Not yet supported on Android"); reply->errorString = tr("Not yet supported on this OS");
return; return;
} }

View File

@ -3,130 +3,36 @@
#include "qdnslookup_p.h" #include "qdnslookup_p.h"
#if QT_CONFIG(library)
#include <qlibrary.h>
#endif
#include <qvarlengtharray.h>
#include <qscopedpointer.h> #include <qscopedpointer.h>
#include <qurl.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 <sys/types.h>
#include <netinet/in.h> #include <netinet/in.h>
#include <arpa/nameser.h> #include <arpa/nameser.h>
#if !defined(Q_OS_OPENBSD) #if __has_include(<arpa/nameser_compat.h>)
# include <arpa/nameser_compat.h> # include <arpa/nameser_compat.h>
#endif #endif
#include <resolv.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>
#endif
#include <cstring>
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals; 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) 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. // Initialize state.
struct __res_state state; std::remove_pointer_t<res_state> state = {};
std::memset(&state, 0, sizeof(state)); if (res_ninit(&state) < 0) {
if (local_res_ninit(&state) < 0) {
reply->error = QDnsLookup::ResolverError; reply->error = QDnsLookup::ResolverError;
reply->errorString = tr("Resolver initialization failed"); reply->errorString = tr("Resolver initialization failed");
return; return;
} }
auto guard = qScopeGuard([&] { res_nclose(&state); });
//Check if a nameserver was set. If so, use it //Check if a nameserver was set. If so, use it
if (!nameserver.isNull()) { if (!nameserver.isNull()) {
@ -170,16 +76,15 @@ void QDnsLookupRunnable::query(const int requestType, const QByteArray &requestN
#ifdef QDNSLOOKUP_DEBUG #ifdef QDNSLOOKUP_DEBUG
state.options |= RES_DEBUG; state.options |= RES_DEBUG;
#endif #endif
QScopedPointer<struct __res_state, QDnsLookupStateDeleter> state_ptr(&state);
// Perform DNS query. // Perform DNS query.
QVarLengthArray<unsigned char, PACKETSZ> buffer(PACKETSZ); QVarLengthArray<unsigned char, PACKETSZ> buffer(PACKETSZ);
std::memset(buffer.data(), 0, buffer.size()); 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)) { if (Q_UNLIKELY(responseLength > PACKETSZ)) {
buffer.resize(responseLength); buffer.resize(responseLength);
std::memset(buffer.data(), 0, buffer.size()); 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())) { if (Q_UNLIKELY(responseLength > buffer.size())) {
// Ok, we give up. // Ok, we give up.
reply->error = QDnsLookup::ResolverError; 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). // Skip the query host, type (2 bytes) and class (2 bytes).
char host[PACKETSZ], answer[PACKETSZ]; char host[PACKETSZ], answer[PACKETSZ];
unsigned char *p = response + sizeof(HEADER); 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) { if (status < 0) {
reply->error = QDnsLookup::InvalidReplyError; reply->error = QDnsLookup::InvalidReplyError;
reply->errorString = tr("Could not expand domain name"); reply->errorString = tr("Could not expand domain name");
@ -240,7 +145,7 @@ void QDnsLookupRunnable::query(const int requestType, const QByteArray &requestN
// Extract results. // Extract results.
int answerIndex = 0; int answerIndex = 0;
while ((p < response + responseLength) && (answerIndex < answerCount)) { 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) { if (status < 0) {
reply->error = QDnsLookup::InvalidReplyError; reply->error = QDnsLookup::InvalidReplyError;
reply->errorString = tr("Could not expand domain name"); 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); record.d->value = QHostAddress(p);
reply->hostAddressRecords.append(record); reply->hostAddressRecords.append(record);
} else if (type == QDnsLookup::CNAME) { } 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) { if (status < 0) {
reply->error = QDnsLookup::InvalidReplyError; reply->error = QDnsLookup::InvalidReplyError;
reply->errorString = tr("Invalid canonical name record"); 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); record.d->value = QUrl::fromAce(answer);
reply->canonicalNameRecords.append(record); reply->canonicalNameRecords.append(record);
} else if (type == QDnsLookup::NS) { } 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) { if (status < 0) {
reply->error = QDnsLookup::InvalidReplyError; reply->error = QDnsLookup::InvalidReplyError;
reply->errorString = tr("Invalid name server record"); 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); record.d->value = QUrl::fromAce(answer);
reply->nameServerRecords.append(record); reply->nameServerRecords.append(record);
} else if (type == QDnsLookup::PTR) { } 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) { if (status < 0) {
reply->error = QDnsLookup::InvalidReplyError; reply->error = QDnsLookup::InvalidReplyError;
reply->errorString = tr("Invalid pointer record"); reply->errorString = tr("Invalid pointer record");
@ -318,7 +223,7 @@ void QDnsLookupRunnable::query(const int requestType, const QByteArray &requestN
reply->pointerRecords.append(record); reply->pointerRecords.append(record);
} else if (type == QDnsLookup::MX) { } else if (type == QDnsLookup::MX) {
const quint16 preference = (p[0] << 8) | p[1]; 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) { if (status < 0) {
reply->error = QDnsLookup::InvalidReplyError; reply->error = QDnsLookup::InvalidReplyError;
reply->errorString = tr("Invalid mail exchange record"); 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 priority = (p[0] << 8) | p[1];
const quint16 weight = (p[2] << 8) | p[3]; const quint16 weight = (p[2] << 8) | p[3];
const quint16 port = (p[4] << 8) | p[5]; 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) { if (status < 0) {
reply->error = QDnsLookup::InvalidReplyError; reply->error = QDnsLookup::InvalidReplyError;
reply->errorString = tr("Invalid service record"); 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 QT_END_NAMESPACE

View File

@ -3,140 +3,75 @@
//#define QHOSTINFO_DEBUG //#define QHOSTINFO_DEBUG
#include "qplatformdefs.h"
#include "qhostinfo_p.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 <sys/types.h>
#include <netdb.h> #include <netdb.h>
#include <arpa/inet.h> #include <netinet/in.h>
#if defined(Q_OS_VXWORKS) #include <resolv.h>
# include <hostLib.h>
#else
# include <resolv.h>
#endif
#if defined(__GNU_LIBRARY__) && !defined(__UCLIBC__) #ifndef _PATH_RESCONF
# include <gnu/lib-names.h> # define _PATH_RESCONF "/etc/resolv.conf"
#endif
#if defined(Q_OS_FREEBSD) || QT_CONFIG(dlopen)
# include <dlfcn.h>
#endif #endif
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals; using namespace Qt::StringLiterals;
enum LibResolvFeature { static void maybeRefreshResolver()
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
{ {
enum { #if defined(RES_NORELOAD)
#ifdef RES_NORELOAD // If RES_NORELOAD is defined, then the libc is capable of watching
// If RES_NORELOAD is defined, then the libc is capable of watching // /etc/resolv.conf for changes and reloading as necessary. So accept
// /etc/resolv.conf for changes and reloading as necessary. So accept // whatever is configured.
// whatever is configured. return;
ReinitNecessary = false #elif defined(Q_OS_DARWIN)
#else // Apple's libsystem_info.dylib:getaddrinfo() uses the
ReinitNecessary = true // 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 #endif
};
QLibrary lib; #if QT_CONFIG(res_ninit)
LibResolv(); // OSes known or thought to reach here: AIX, NetBSD, Solaris,
~LibResolv() { lib.unload(); } // Linux with MUSL (though res_init() does nothing and is unnecessary)
};
}
static QFunctionPointer resolveSymbol(QLibrary &lib, const char *sym) Q_CONSTINIT static QT_STATBUF lastStat = {};
{ Q_CONSTINIT static QBasicMutex mutex = {};
if (lib.isLoaded()) if (QT_STATBUF st; QT_STAT(_PATH_RESCONF, &st) == 0) {
return lib.resolve(sym); QMutexLocker locker(&mutex);
bool refresh = false;
#if defined(RTLD_DEFAULT) && (defined(Q_OS_FREEBSD) || QT_CONFIG(dlopen)) if ((_res.options & RES_INIT) == 0)
return reinterpret_cast<QFunctionPointer>(dlsym(RTLD_DEFAULT, sym)); refresh = true;
#else else if (lastStat.st_ctime != st.st_ctime)
return nullptr; refresh = true; // file was updated
#endif else if (lastStat.st_dev != st.st_dev || lastStat.st_ino != st.st_ino)
} refresh = true; // file was replaced
if (refresh) {
LibResolv::LibResolv() lastStat = st;
{ res_init();
#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"));
} }
} }
#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 QHostInfoAgent::fromName(const QString &hostName)
{ {
QHostInfo results; QHostInfo results;
@ -146,12 +81,7 @@ QHostInfo QHostInfoAgent::fromName(const QString &hostName)
hostName.toLatin1().constData()); hostName.toLatin1().constData());
#endif #endif
// Load res_init on demand. maybeRefreshResolver();
resolveLibrary(NeedResInit);
// If res_init is available, poll it.
if (local_res_init)
local_res_init();
QHostAddress address; QHostAddress address;
if (address.setAddress(hostName)) if (address.setAddress(hostName))
@ -162,40 +92,30 @@ QHostInfo QHostInfoAgent::fromName(const QString &hostName)
QString QHostInfo::localDomainName() QString QHostInfo::localDomainName()
{ {
#if !defined(Q_OS_VXWORKS) && !defined(Q_OS_ANDROID) #if QT_CONFIG(res_ninit)
resolveLibrary(NeedResNInit); auto domainNameFromRes = [](res_state r) {
if (local_res_ninit) { 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 // using thread-safe version
res_state_ptr state = res_state_ptr(malloc(sizeof(*state))); auto guard = qScopeGuard([&] { res_nclose(&state); });
Q_CHECK_PTR(state); return domainNameFromRes(&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;
} }
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: // nothing worked, try doing it by ourselves:
QFile resolvconf; QFile resolvconf;
#if defined(_PATH_RESCONF) resolvconf.setFileName(_PATH_RESCONF ""_L1);
resolvconf.setFileName(QFile::decodeName(_PATH_RESCONF));
#else
resolvconf.setFileName("/etc/resolv.conf"_L1);
#endif
if (!resolvconf.open(QIODevice::ReadOnly)) if (!resolvconf.open(QIODevice::ReadOnly))
return QString(); // failure return QString(); // failure

View File

@ -1,7 +1,7 @@
# Copyright (C) 2022 The Qt Company Ltd. # Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause # SPDX-License-Identifier: BSD-3-Clause
if(NOT INTEGRITY) if(WIN32 OR TEST_res_ninit)
add_subdirectory(qdnslookup) add_subdirectory(qdnslookup)
add_subdirectory(qdnslookup_appless) add_subdirectory(qdnslookup_appless)
endif() endif()

View File

@ -202,11 +202,6 @@ void tst_QDnsLookup::lookup()
lookup.lookup(); lookup.lookup();
QTRY_VERIFY_WITH_TIMEOUT(lookup.isFinished(), Timeout); 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, QVERIFY2(int(lookup.error()) == error,
msgDnsLookup(lookup.error(), error, domain, cname, host, srv, mx, ns, ptr, lookup.errorString())); msgDnsLookup(lookup.error(), error, domain, cname, host, srv, mx, ns, ptr, lookup.errorString()));
if (error == QDnsLookup::NoError) if (error == QDnsLookup::NoError)
@ -301,11 +296,6 @@ void tst_QDnsLookup::lookupReuse()
lookup.lookup(); lookup.lookup();
QTRY_VERIFY_WITH_TIMEOUT(lookup.isFinished(), Timeout); 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)); QCOMPARE(int(lookup.error()), int(QDnsLookup::NoError));
QVERIFY(!lookup.hostAddressRecords().isEmpty()); QVERIFY(!lookup.hostAddressRecords().isEmpty());
QCOMPARE(lookup.hostAddressRecords().first().name(), domainName("a-single")); QCOMPARE(lookup.hostAddressRecords().first().name(), domainName("a-single"));
@ -342,11 +332,6 @@ void tst_QDnsLookup::lookupAbortRetry()
lookup.lookup(); lookup.lookup();
QTRY_VERIFY_WITH_TIMEOUT(lookup.isFinished(), Timeout); 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)); QCOMPARE(int(lookup.error()), int(QDnsLookup::NoError));
QVERIFY(!lookup.hostAddressRecords().isEmpty()); QVERIFY(!lookup.hostAddressRecords().isEmpty());
QCOMPARE(lookup.hostAddressRecords().first().name(), domainName("aaaa-single")); QCOMPARE(lookup.hostAddressRecords().first().name(), domainName("aaaa-single"));

View File

@ -506,6 +506,7 @@ protected:
inline void run() override inline void run() override
{ {
QHostInfo info = QHostInfo::fromName("a-single" TEST_DOMAIN); QHostInfo info = QHostInfo::fromName("a-single" TEST_DOMAIN);
QCOMPARE(info.errorString(), "Unknown error"); // no error
QCOMPARE(info.error(), QHostInfo::NoError); QCOMPARE(info.error(), QHostInfo::NoError);
QVERIFY(info.addresses().size() > 0); QVERIFY(info.addresses().size() > 0);
QCOMPARE(info.addresses().at(0).toString(), QString("192.0.2.1")); QCOMPARE(info.addresses().at(0).toString(), QString("192.0.2.1"));