Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qhostinfo.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
5//#define QHOSTINFO_DEBUG
6
7#include "qhostinfo.h"
8#include "qhostinfo_p.h"
9#include <qplatformdefs.h>
10
11#include "QtCore/qapplicationstatic.h"
12#include <qabstracteventdispatcher.h>
13#include <qcoreapplication.h>
14#include <qmetaobject.h>
15#include <qscopeguard.h>
16#include <qstringlist.h>
17#include <qthread.h>
18#include <qurl.h>
19
20#include <private/qobject_p.h>
21
22#include <algorithm>
23
24#ifdef Q_OS_UNIX
25# include <unistd.h>
26# include <netdb.h>
27# include <netinet/in.h>
28# if defined(AI_ADDRCONFIG) && !defined(Q_OS_WASM)
29# define Q_ADDRCONFIG AI_ADDRCONFIG
30# endif
31#elif defined Q_OS_WIN
32# include <ws2tcpip.h>
33
34# define QT_SOCKLEN_T int
35#endif
36
37QT_BEGIN_NAMESPACE
38
39using namespace Qt::StringLiterals;
40
41//#define QHOSTINFO_DEBUG
42
44
45namespace {
46struct ToBeLookedUpEquals {
47 typedef bool result_type;
48 explicit ToBeLookedUpEquals(const QString &toBeLookedUp) noexcept : m_toBeLookedUp(toBeLookedUp) {}
49 result_type operator()(QHostInfoRunnable* lookup) const noexcept
50 {
51 return m_toBeLookedUp == lookup->toBeLookedUp;
52 }
53private:
54 QString m_toBeLookedUp;
55};
56
57template <typename InputIt, typename OutputIt1, typename OutputIt2, typename UnaryPredicate>
58std::pair<OutputIt1, OutputIt2> separate_if(InputIt first, InputIt last, OutputIt1 dest1, OutputIt2 dest2, UnaryPredicate p)
59{
60 while (first != last) {
61 if (p(*first)) {
62 *dest1 = *first;
63 ++dest1;
64 } else {
65 *dest2 = *first;
66 ++dest2;
67 }
68 ++first;
69 }
70 return std::make_pair(dest1, dest2);
71}
72
73Q_APPLICATION_STATIC(QHostInfoLookupManager, theHostInfoLookupManager)
74
75}
76
77QHostInfoResult::QHostInfoResult(const QObject *receiver, QtPrivate::SlotObjUniquePtr slot)
78 : receiver{receiver ? receiver : this}, slotObj{std::move(slot)}
79{
80 Q_ASSERT(this->receiver);
81 moveToThread(this->receiver->thread());
82}
83
84QHostInfoResult::~QHostInfoResult()
85 = default;
86
87/*
88 The calling thread is likely the one that executes the lookup via
89 QHostInfoRunnable. Unless we operate with a queued connection already,
90 posts the QHostInfo to a dedicated QHostInfoResult object that lives in
91 the same thread as the user-provided receiver, or (if there is none) in
92 the thread that made the call to lookupHost. That QHostInfoResult object
93 then calls the user code in the correct thread.
94
95 The 'result' object deletes itself (via deleteLater) when
96 finalizePostResultsReady is called.
97*/
98void QHostInfoResult::postResultsReady(const QHostInfo &info)
99{
100 // queued connection will take care of dispatching to right thread
101 if (!slotObj) {
102 emit resultsReady(info);
103 return;
104 }
105 // we used to have a context object, but it's already destroyed
106 if (!receiver)
107 return;
108
109 // a long-living version of this
110 auto result = new QHostInfoResult(this);
111 Q_CHECK_PTR(result);
112
113 QMetaObject::invokeMethod(result,
114 &QHostInfoResult::finalizePostResultsReady,
115 Qt::QueuedConnection,
116 info);
117}
118
119/*
120 Receives the info from postResultsReady, and calls the functor.
121*/
122void QHostInfoResult::finalizePostResultsReady(const QHostInfo &info)
123{
124 Q_ASSERT(slotObj);
125
126 // we used to have a context object, but it's already destroyed
127 if (receiver) {
128 void *args[] = { nullptr, const_cast<QHostInfo *>(&info) };
129 slotObj->call(const_cast<QObject *>(receiver.data()), args);
130 }
131
132 deleteLater();
133}
134
135/*!
136 \class QHostInfo
137 \brief The QHostInfo class provides static functions for host name lookups.
138
139 \reentrant
140 \inmodule QtNetwork
141 \ingroup network
142
143 QHostInfo finds the IP address(es) associated with a host name,
144 or the host name associated with an IP address.
145 The class provides two static convenience functions: one that
146 works asynchronously and emits a signal once the host is found,
147 and one that blocks and returns a QHostInfo object.
148
149 To look up a host's IP addresses asynchronously, call lookupHost(),
150 which takes the host name or IP address, a receiver object, and a slot
151 signature as arguments and returns an ID. You can abort the
152 lookup by calling abortHostLookup() with the lookup ID.
153
154 Example:
155
156 \snippet code/src_network_kernel_qhostinfo.cpp 0
157
158
159 The slot is invoked when the results are ready. The results are
160 stored in a QHostInfo object. Call
161 addresses() to get the list of IP addresses for the host, and
162 hostName() to get the host name that was looked up.
163
164 If the lookup failed, error() returns the type of error that
165 occurred. errorString() gives a human-readable description of the
166 lookup error.
167
168 If you want a blocking lookup, use the QHostInfo::fromName() function:
169
170 \snippet code/src_network_kernel_qhostinfo.cpp 1
171
172 QHostInfo supports Internationalized Domain Names (IDNs) through the
173 IDNA and Punycode standards.
174
175 To retrieve the name of the local host, use the static
176 QHostInfo::localHostName() function.
177
178 QHostInfo uses the mechanisms provided by the operating system
179 to perform the lookup. As per \l {RFC 6724}
180 there is no guarantee that all IP addresses registered for a domain or
181 host will be returned.
182
183 \note Since Qt 4.6.1 QHostInfo is using multiple threads for DNS lookup
184 instead of one dedicated DNS thread. This improves performance,
185 but also changes the order of signal emissions when using lookupHost()
186 compared to previous versions of Qt.
187 \note Since Qt 4.6.3 QHostInfo is using a small internal 60 second DNS cache
188 for performance improvements.
189
190 \sa QAbstractSocket, {RFC 3492}, {RFC 6724}
191*/
192
193static int nextId()
194{
195 Q_CONSTINIT static QBasicAtomicInt counter = Q_BASIC_ATOMIC_INITIALIZER(0);
196 return 1 + counter.fetchAndAddRelaxed(1);
197}
198
199/*!
200 \threadsafe
201 Looks up the IP address(es) associated with host name \a name, and
202 returns an ID for the lookup. When the result of the lookup is
203 ready, the slot or signal \a member in \a receiver is called with
204 a QHostInfo argument. The QHostInfo object can then be inspected
205 to get the results of the lookup.
206
207 The lookup is performed by a single function call, for example:
208
209 \snippet code/src_network_kernel_qhostinfo.cpp 2
210
211 The implementation of the slot prints basic information about the
212 addresses returned by the lookup, or reports an error if it failed:
213
214 \snippet code/src_network_kernel_qhostinfo.cpp 3
215
216 If you pass a literal IP address to \a name instead of a host name,
217 QHostInfo will search for the domain name for the IP (i.e., QHostInfo will
218 perform a \e reverse lookup). On success, the resulting QHostInfo will
219 contain both the resolved domain name and IP addresses for the host
220 name. Example:
221
222 \snippet code/src_network_kernel_qhostinfo.cpp 4
223
224 \note There is no guarantee on the order the signals will be emitted
225 if you start multiple requests with lookupHost().
226
227 \note In Qt versions prior to 6.7, this function took \a receiver as
228 (non-const) \c{QObject*}.
229
230 \sa abortHostLookup(), addresses(), error(), fromName()
231*/
232int QHostInfo::lookupHost(const QString &name, const QObject *receiver, const char *member)
233{
234 if (!receiver || !member) {
235 qWarning("QHostInfo::lookupHost: both the receiver and the member to invoke must be non-null");
236 return -1;
237 }
238 return QHostInfo::lookupHostImpl(name, receiver, nullptr, member);
239}
240
241/*!
242 \fn QHostInfo &QHostInfo::operator=(QHostInfo &&other)
243
244 Move-assigns \a other to this QHostInfo instance.
245
246 \note The moved-from object \a other is placed in a
247 partially-formed state, in which the only valid operations are
248 destruction and assignment of a new value.
249
250 \since 5.10
251*/
252
253/*!
254 \fn void QHostInfo::swap(QHostInfo &other)
255 \memberswap{host-info}
256 \since 5.10
257*/
258
259/*!
260 \fn template<typename Functor> int QHostInfo::lookupHost(const QString &name, Functor &&functor)
261 \threadsafe
262
263 \since 5.9
264
265 \overload
266
267 Looks up the IP address(es) associated with host name \a name, and
268 returns an ID for the lookup. When the result of the lookup is
269 ready, the \a functor is called with a QHostInfo argument. The
270 QHostInfo object can then be inspected to get the results of the
271 lookup.
272
273 The \a functor will be run in the thread that makes the call to lookupHost;
274 that thread must have a running Qt event loop.
275
276 \note There is no guarantee on the order the signals will be emitted
277 if you start multiple requests with lookupHost().
278
279 \sa abortHostLookup(), addresses(), error(), fromName()
280*/
281
282/*!
283 \fn template<typename Functor> int QHostInfo::lookupHost(const QString &name, const QObject *context, Functor functor)
284 \threadsafe
285
286 \since 5.9
287
288 \overload
289
290 Looks up the IP address(es) associated with host name \a name, and
291 returns an ID for the lookup. When the result of the lookup is
292 ready, the \a functor is called with a QHostInfo argument. The
293 QHostInfo object can then be inspected to get the results of the
294 lookup.
295
296 If \a context is destroyed before the lookup completes, the
297 \a functor will not be called. The \a functor will be run in the
298 thread of \a context. The context's thread must have a running Qt
299 event loop.
300
301 Here is an alternative signature for the function:
302 \code
303 lookupHost(const QString &name, const QObject *receiver, PointerToMemberFunction function)
304 \endcode
305
306 In this case, when the result of the lookup is ready, the slot or
307 signal \c{function} in \c{receiver} is called with a QHostInfo
308 argument. The QHostInfo object can then be inspected to get the
309 results of the lookup.
310
311 \note There is no guarantee on the order the signals will be emitted
312 if you start multiple requests with lookupHost().
313
314 \sa abortHostLookup(), addresses(), error(), fromName()
315*/
316
317/*!
318 \threadsafe
319 Aborts the host lookup with the ID \a id, as returned by lookupHost().
320
321 \sa lookupHost(), lookupId()
322*/
323void QHostInfo::abortHostLookup(int id)
324{
325 theHostInfoLookupManager()->abortLookup(id);
326}
327
328/*!
329 \threadsafe
330 Looks up the IP address(es) for the given host \a name. The
331 function blocks during the lookup which means that execution of
332 the program is suspended until the results of the lookup are
333 ready. Returns the result of the lookup in a QHostInfo object.
334
335 If you pass a literal IP address to \a name instead of a host name,
336 QHostInfo will search for the domain name for the IP (i.e., QHostInfo will
337 perform a \e reverse lookup). On success, the returned QHostInfo will
338 contain both the resolved domain name and IP addresses for the host name.
339
340 \sa lookupHost()
341*/
342QHostInfo QHostInfo::fromName(const QString &name)
343{
344#if defined QHOSTINFO_DEBUG
345 qDebug("QHostInfo::fromName(\"%s\")",name.toLatin1().constData());
346#endif
347
348#ifdef Q_OS_WASM
349 return QHostInfoAgent::lookup(name);
350#else
351 QHostInfo hostInfo = QHostInfoAgent::fromName(name);
352 QHostInfoLookupManager* manager = theHostInfoLookupManager();
353 manager->cache.put(name, hostInfo);
354 return hostInfo;
355#endif
356}
357
358
359QHostInfo QHostInfoAgent::reverseLookup(const QHostAddress &address)
360{
361 QHostInfo results;
362 // Reverse lookup
363 sockaddr_in sa4;
364 sockaddr_in6 sa6;
365 sockaddr *sa = nullptr;
366 QT_SOCKLEN_T saSize;
367 if (address.protocol() == QHostAddress::IPv4Protocol) {
368 sa = reinterpret_cast<sockaddr *>(&sa4);
369 saSize = sizeof(sa4);
370 memset(&sa4, 0, sizeof(sa4));
371 sa4.sin_family = AF_INET;
372 sa4.sin_addr.s_addr = htonl(address.toIPv4Address());
373 } else {
374 sa = reinterpret_cast<sockaddr *>(&sa6);
375 saSize = sizeof(sa6);
376 memset(&sa6, 0, sizeof(sa6));
377 sa6.sin6_family = AF_INET6;
378 memcpy(&sa6.sin6_addr, address.toIPv6Address().c, sizeof(sa6.sin6_addr));
379 }
380
381 char hbuf[NI_MAXHOST];
382 if (sa && getnameinfo(sa, saSize, hbuf, sizeof(hbuf), nullptr, 0, 0) == 0)
383 results.setHostName(QString::fromLatin1(hbuf));
384
385 if (results.hostName().isEmpty())
386 results.setHostName(address.toString());
387 results.setAddresses(QList<QHostAddress>() << address);
388
389 return results;
390}
391
392/*
393 Call getaddrinfo, and returns the results as QHostInfo::addresses
394*/
395QHostInfo QHostInfoAgent::lookup(const QString &hostName)
396{
397 QHostInfo results;
398
399 // IDN support
400 QByteArray aceHostname = QUrl::toAce(hostName);
401 results.setHostName(hostName);
402 if (aceHostname.isEmpty()) {
403 results.setError(QHostInfo::HostNotFound);
404 results.setErrorString(hostName.isEmpty() ?
405 QCoreApplication::translate("QHostInfoAgent", "No host name given") :
406 QCoreApplication::translate("QHostInfoAgent", "Invalid hostname"));
407 return results;
408 }
409
410 addrinfo *res = nullptr;
411 struct addrinfo hints;
412 memset(&hints, 0, sizeof(hints));
413 hints.ai_family = PF_UNSPEC;
414#ifdef Q_ADDRCONFIG
415 hints.ai_flags = Q_ADDRCONFIG;
416#endif
417
418 int result = getaddrinfo(aceHostname.constData(), nullptr, &hints, &res);
419# ifdef Q_ADDRCONFIG
420 if (result == EAI_BADFLAGS) {
421 // if the lookup failed with AI_ADDRCONFIG set, try again without it
422 hints.ai_flags = 0;
423 result = getaddrinfo(aceHostname.constData(), nullptr, &hints, &res);
424 }
425# endif
426
427 if (result == 0) {
428 addrinfo *node = res;
429 QList<QHostAddress> addresses;
430 while (node) {
431#ifdef QHOSTINFO_DEBUG
432 qDebug() << "getaddrinfo node: flags:" << node->ai_flags << "family:" << node->ai_family
433 << "ai_socktype:" << node->ai_socktype << "ai_protocol:" << node->ai_protocol
434 << "ai_addrlen:" << node->ai_addrlen;
435#endif
436 switch (node->ai_family) {
437 case AF_INET: {
438 QHostAddress addr;
439 addr.setAddress(ntohl(((sockaddr_in *) node->ai_addr)->sin_addr.s_addr));
440 if (!addresses.contains(addr))
441 addresses.append(addr);
442 break;
443 }
444 case AF_INET6: {
445 QHostAddress addr;
446 sockaddr_in6 *sa6 = (sockaddr_in6 *) node->ai_addr;
447 addr.setAddress(sa6->sin6_addr.s6_addr);
448 if (sa6->sin6_scope_id)
449 addr.setScopeId(QString::number(sa6->sin6_scope_id));
450 if (!addresses.contains(addr))
451 addresses.append(addr);
452 break;
453 }
454 default:
455 results.setError(QHostInfo::UnknownError);
456 results.setErrorString(QCoreApplication::translate("QHostInfoAgent", "Unknown address type"));
457 }
458 node = node->ai_next;
459 }
460 if (addresses.isEmpty()) {
461 // Reached the end of the list, but no addresses were found; this
462 // means the list contains one or more unknown address types.
463 results.setError(QHostInfo::UnknownError);
464 results.setErrorString(QCoreApplication::translate("QHostInfoAgent", "Unknown address type"));
465 }
466
467 results.setAddresses(addresses);
468 freeaddrinfo(res);
469 } else {
470 switch (result) {
471#ifdef Q_OS_WIN
472 case WSAHOST_NOT_FOUND: //authoritative not found
473 case WSATRY_AGAIN: //non authoritative not found
474 case WSANO_DATA: //valid name, no associated address
475#else
476 case EAI_NONAME:
477 case EAI_FAIL:
478# ifdef EAI_NODATA // EAI_NODATA is deprecated in RFC 3493
479 case EAI_NODATA:
480# endif
481#endif
482 results.setError(QHostInfo::HostNotFound);
483 results.setErrorString(QCoreApplication::translate("QHostInfoAgent", "Host not found"));
484 break;
485 default:
486 results.setError(QHostInfo::UnknownError);
487#ifdef Q_OS_WIN
488 results.setErrorString(QString::fromWCharArray(gai_strerror(result)));
489#else
490 results.setErrorString(QString::fromLocal8Bit(gai_strerror(result)));
491#endif
492 break;
493 }
494 }
495
496#if defined(QHOSTINFO_DEBUG)
497 if (results.error() != QHostInfo::NoError) {
498 qDebug("QHostInfoAgent::fromName(): error #%d %s",
499 h_errno, results.errorString().toLatin1().constData());
500 } else {
501 QString tmp;
502 QList<QHostAddress> addresses = results.addresses();
503 for (int i = 0; i < addresses.count(); ++i) {
504 if (i != 0) tmp += ", "_L1;
505 tmp += addresses.at(i).toString();
506 }
507 qDebug("QHostInfoAgent::fromName(): found %i entries for \"%s\": {%s}",
508 addresses.count(), aceHostname.constData(),
509 tmp.toLatin1().constData());
510 }
511#endif
512
513 return results;
514}
515
516/*!
517 \enum QHostInfo::HostInfoError
518
519 This enum describes the various errors that can occur when trying
520 to resolve a host name.
521
522 \value NoError The lookup was successful.
523 \value HostNotFound No IP addresses were found for the host.
524 \value UnknownError An unknown error occurred.
525
526 \sa error(), setError()
527*/
528
529/*!
530 Constructs an empty host info object with lookup ID \a id.
531
532 \sa lookupId()
533*/
534QHostInfo::QHostInfo(int id)
535 : d_ptr(new QHostInfoPrivate)
536{
537 Q_D(QHostInfo);
538 d->lookupId = id;
539}
540
541/*!
542 Constructs a copy of \a other.
543*/
544QHostInfo::QHostInfo(const QHostInfo &other)
545 : d_ptr(new QHostInfoPrivate(*other.d_ptr))
546{
547}
548
549/*!
550 \fn QHostInfo::QHostInfo(QHostInfo &&other)
551
552 Move-constructs a new QHostInfo from \a other.
553
554 \note The moved-from object \a other is placed in a
555 partially-formed state, in which the only valid operations are
556 destruction and assignment of a new value.
557
558 \since 5.14
559*/
560
561/*!
562 Assigns the data of the \a other object to this host info object,
563 and returns a reference to it.
564*/
565QHostInfo &QHostInfo::operator=(const QHostInfo &other)
566{
567 if (this == &other)
568 return *this;
569
570 Q_ASSERT(d_ptr && other.d_ptr);
571 *d_ptr = *other.d_ptr;
572 return *this;
573}
574
575/*!
576 Destroys the host info object.
577*/
578QHostInfo::~QHostInfo()
579{
580 delete d_ptr;
581}
582
583/*!
584 Returns the list of IP addresses associated with hostName(). This
585 list may be empty.
586
587 Example:
588
589 \snippet code/src_network_kernel_qhostinfo.cpp 5
590
591 \sa hostName(), error()
592*/
593QList<QHostAddress> QHostInfo::addresses() const
594{
595 Q_D(const QHostInfo);
596 return d->addrs;
597}
598
599/*!
600 Sets the list of addresses in this QHostInfo to \a addresses.
601
602 \sa addresses()
603*/
604void QHostInfo::setAddresses(const QList<QHostAddress> &addresses)
605{
606 Q_D(QHostInfo);
607 d->addrs = addresses;
608}
609
610/*!
611 Returns the name of the host whose IP addresses were looked up.
612
613 \sa localHostName()
614*/
615QString QHostInfo::hostName() const
616{
617 Q_D(const QHostInfo);
618 return d->hostName;
619}
620
621/*!
622 Sets the host name of this QHostInfo to \a hostName.
623
624 \sa hostName()
625*/
626void QHostInfo::setHostName(const QString &hostName)
627{
628 Q_D(QHostInfo);
629 d->hostName = hostName;
630}
631
632/*!
633 Returns the type of error that occurred if the host name lookup
634 failed; otherwise returns NoError.
635
636 \sa setError(), errorString()
637*/
638QHostInfo::HostInfoError QHostInfo::error() const
639{
640 Q_D(const QHostInfo);
641 return d->err;
642}
643
644/*!
645 Sets the error type of this QHostInfo to \a error.
646
647 \sa error(), errorString()
648*/
649void QHostInfo::setError(HostInfoError error)
650{
651 Q_D(QHostInfo);
652 d->err = error;
653}
654
655/*!
656 Returns the ID of this lookup.
657
658 \sa setLookupId(), abortHostLookup(), hostName()
659*/
660int QHostInfo::lookupId() const
661{
662 Q_D(const QHostInfo);
663 return d->lookupId;
664}
665
666/*!
667 Sets the ID of this lookup to \a id.
668
669 \sa lookupId(), lookupHost()
670*/
671void QHostInfo::setLookupId(int id)
672{
673 Q_D(QHostInfo);
674 d->lookupId = id;
675}
676
677/*!
678 If the lookup failed, this function returns a human readable
679 description of the error; otherwise "Unknown error" is returned.
680
681 \sa setErrorString(), error()
682*/
683QString QHostInfo::errorString() const
684{
685 Q_D(const QHostInfo);
686 return d->errorStr;
687}
688
689/*!
690 Sets the human readable description of the error that occurred to \a str
691 if the lookup failed.
692
693 \sa errorString(), setError()
694*/
695void QHostInfo::setErrorString(const QString &str)
696{
697 Q_D(QHostInfo);
698 d->errorStr = str;
699}
700
701/*!
702 \fn QString QHostInfo::localHostName()
703 \threadsafe
704
705 Returns this machine's host name, if one is configured. Note that hostnames
706 are not guaranteed to be globally unique, especially if they were
707 configured automatically.
708
709 This function does not guarantee the returned host name is a Fully
710 Qualified Domain Name (FQDN). For that, use fromName() to resolve the
711 returned name to an FQDN.
712
713 This function returns the same as QSysInfo::machineHostName().
714
715 \sa hostName(), localDomainName()
716*/
717QString QHostInfo::localHostName()
718{
719 return QSysInfo::machineHostName();
720}
721
722/*!
723 \fn QString QHostInfo::localDomainName()
724 \threadsafe
725
726 Returns the DNS domain of this machine.
727
728 \note DNS domains are not related to domain names found in
729 Windows networks.
730
731 \sa hostName()
732*/
733
734/*!
735 \internal
736 Called by the various lookupHost overloads to perform the lookup.
737
738 Signals either the functor encapuslated in the \a slotObjRaw in the context
739 of \a receiver, or the \a member slot of the \a receiver.
740
741 \a receiver might be the nullptr, but only if a \a slotObjRaw is provided.
742*/
743int QHostInfo::lookupHostImpl(const QString &name,
744 const QObject *receiver,
745 QtPrivate::QSlotObjectBase *slotObjRaw,
746 const char *member)
747{
748 QtPrivate::SlotObjUniquePtr slotObj{slotObjRaw};
749#if defined QHOSTINFO_DEBUG
750 qDebug("QHostInfo::lookupHostImpl(\"%s\", %p, %p, %s)",
751 name.toLatin1().constData(), receiver, slotObj.get(), member ? member + 1 : 0);
752#endif
753 Q_ASSERT(!member != !slotObj); // one of these must be set, but not both
754 Q_ASSERT(receiver || slotObj);
755 Q_ASSERT(!member || receiver); // if member is set, also is receiver
756 const bool isUsingStringBasedSlot = static_cast<bool>(member);
757
758 if (!QAbstractEventDispatcher::instance()) {
759 qWarning("QHostInfo::lookupHost() called with no event dispatcher");
760 return -1;
761 }
762
763 qRegisterMetaType<QHostInfo>();
764
765 int id = nextId(); // generate unique ID
766
767 if (Q_UNLIKELY(name.isEmpty())) {
768 QHostInfo hostInfo(id);
769 hostInfo.setError(QHostInfo::HostNotFound);
770 hostInfo.setErrorString(QCoreApplication::translate("QHostInfo", "No host name given"));
771
772 QHostInfoResult result(receiver, std::move(slotObj));
773 if (isUsingStringBasedSlot) {
774 QObject::connect(&result, SIGNAL(resultsReady(QHostInfo)),
775 receiver, member, Qt::QueuedConnection);
776 }
777 result.postResultsReady(hostInfo);
778
779 return id;
780 }
781
782#ifdef Q_OS_WASM
783 // Resolve the host name directly without using a thread or cache,
784 // since Emscripten's host lookup is fast. Emscripten maintains an internal
785 // mapping of hosts and addresses for the purposes of WebSocket socket
786 // tunnelling, and does not perform an actual host lookup.
787 QHostInfo hostInfo = QHostInfoAgent::lookup(name);
788 hostInfo.setLookupId(id);
789
790 QHostInfoResult result(receiver, std::move(slotObj));
791 if (isUsingStringBasedSlot) {
792 QObject::connect(&result, SIGNAL(resultsReady(QHostInfo)),
793 receiver, member, Qt::QueuedConnection);
794 }
795 result.postResultsReady(hostInfo);
796#else
797 QHostInfoLookupManager *manager = theHostInfoLookupManager();
798
799 if (Q_LIKELY(manager)) {
800 // the application is still alive
801 if (manager->cache.isEnabled()) {
802 // check cache first
803 bool valid = false;
804 QHostInfo info = manager->cache.get(name, &valid);
805 if (valid) {
806 info.setLookupId(id);
807 QHostInfoResult result(receiver, std::move(slotObj));
808 if (isUsingStringBasedSlot) {
809 QObject::connect(&result, SIGNAL(resultsReady(QHostInfo)),
810 receiver, member, Qt::QueuedConnection);
811 }
812 result.postResultsReady(info);
813 return id;
814 }
815 }
816
817 // cache is not enabled or it was not in the cache, do normal lookup
818 QHostInfoRunnable *runnable = new QHostInfoRunnable(name, id, receiver, std::move(slotObj));
819 if (isUsingStringBasedSlot) {
820 QObject::connect(runnable->resultEmitter.get(), SIGNAL(resultsReady(QHostInfo)),
821 receiver, member, Qt::QueuedConnection);
822 }
823 manager->scheduleLookup(runnable);
824 }
825#endif // Q_OS_WASM
826 return id;
827}
828
829QHostInfoRunnable::QHostInfoRunnable(const QString &hn, int i, const QObject *receiver,
830 QtPrivate::SlotObjUniquePtr slotObj)
831 : toBeLookedUp{hn}, id{i}, resultEmitter{new QHostInfoResult(receiver, std::move(slotObj))}
832{
833 setAutoDelete(true);
834}
835
836QHostInfoRunnable::~QHostInfoRunnable()
837{
838 // resultEmitter belongs to the receiver's thread, which is rarely the one deleting this
839 // runnable, so it can't be destroyed here directly.
840 QObjectPrivate::deleteInOwnThread(resultEmitter.release());
841}
842
843// the QHostInfoLookupManager will at some point call this via a QThreadPool
844void QHostInfoRunnable::run()
845{
846 QHostInfoLookupManager *manager = theHostInfoLookupManager();
847 const auto sg = qScopeGuard([&] { manager->lookupFinished(this); });
848 // check aborted
849 if (manager->wasAborted(id))
850 return;
851
852 QHostInfo hostInfo;
853
854 // QHostInfo::lookupHost already checks the cache. However we need to check
855 // it here too because it might have been cache saved by another QHostInfoRunnable
856 // in the meanwhile while this QHostInfoRunnable was scheduled but not running
857 if (manager->cache.isEnabled()) {
858 // check the cache first
859 bool valid = false;
860 hostInfo = manager->cache.get(toBeLookedUp, &valid);
861 if (!valid) {
862 // not in cache, we need to do the lookup and store the result in the cache
863 hostInfo = QHostInfoAgent::fromName(toBeLookedUp);
864 manager->cache.put(toBeLookedUp, hostInfo);
865 }
866 } else {
867 // cache is not enabled, just do the lookup and continue
868 hostInfo = QHostInfoAgent::fromName(toBeLookedUp);
869 }
870
871 // check aborted again
872 if (manager->wasAborted(id))
873 return;
874
875 // signal emission
876 hostInfo.setLookupId(id);
877 resultEmitter->postResultsReady(hostInfo);
878
879#if QT_CONFIG(thread)
880 // now also iterate through the postponed ones
881 {
882 QMutexLocker locker(&manager->mutex);
883 const auto partitionBegin = std::stable_partition(manager->postponedLookups.rbegin(), manager->postponedLookups.rend(),
884 ToBeLookedUpEquals(toBeLookedUp)).base();
885 const auto partitionEnd = manager->postponedLookups.end();
886 for (auto it = partitionBegin; it != partitionEnd; ++it) {
887 QHostInfoRunnable* postponed = *it;
888 // we can now emit
889 hostInfo.setLookupId(postponed->id);
890 postponed->resultEmitter->postResultsReady(hostInfo);
891 delete postponed;
892 }
893 manager->postponedLookups.erase(partitionBegin, partitionEnd);
894 }
895
896#endif
897 // thread goes back to QThreadPool
898}
899
900QHostInfoLookupManager::QHostInfoLookupManager() : wasDeleted(false)
901{
902#if QT_CONFIG(thread)
903 QObject::connect(QCoreApplication::instance(), &QObject::destroyed,
904 &threadPool, [&](QObject *) { threadPool.waitForDone(); },
905 Qt::DirectConnection);
906 threadPool.setMaxThreadCount(20); // do up to 20 DNS lookups in parallel
907#endif
908}
909
910QHostInfoLookupManager::~QHostInfoLookupManager()
911{
912 QMutexLocker locker(&mutex);
913 wasDeleted = true;
914 locker.unlock();
915
916 // don't qDeleteAll currentLookups, the QThreadPool has ownership
917 clear();
918}
919
920void QHostInfoLookupManager::clear()
921{
922 {
923 QMutexLocker locker(&mutex);
924 qDeleteAll(scheduledLookups);
925 qDeleteAll(finishedLookups);
926#if QT_CONFIG(thread)
927 qDeleteAll(postponedLookups);
928 postponedLookups.clear();
929#endif
930 scheduledLookups.clear();
931 finishedLookups.clear();
932 }
933
934#if QT_CONFIG(thread)
935 threadPool.waitForDone();
936#endif
937 cache.clear();
938}
939
940// assumes mutex is locked by caller
941void QHostInfoLookupManager::rescheduleWithMutexHeld()
942{
943 if (wasDeleted)
944 return;
945
946 // goals of this function:
947 // - launch new lookups via the thread pool
948 // - make sure only one lookup per host/IP is in progress
949
950 if (!finishedLookups.isEmpty()) {
951 // remove ID from aborted if it is in there
952 for (int i = 0; i < finishedLookups.size(); i++) {
953 abortedLookups.removeAll(finishedLookups.at(i)->id);
954 }
955
956 finishedLookups.clear();
957 }
958
959#if QT_CONFIG(thread)
960 auto isAlreadyRunning = [this](QHostInfoRunnable *lookup) {
961 return std::any_of(currentLookups.cbegin(), currentLookups.cend(), ToBeLookedUpEquals(lookup->toBeLookedUp));
962 };
963
964 // Transfer any postponed lookups that aren't currently running to the scheduled list, keeping already-running lookups:
965 postponedLookups.erase(separate_if(postponedLookups.begin(),
966 postponedLookups.end(),
967 postponedLookups.begin(),
968 std::front_inserter(scheduledLookups), // prepend! we want to finish it ASAP
969 isAlreadyRunning).first,
970 postponedLookups.end());
971
972 // Unschedule and postpone any that are currently running:
973 scheduledLookups.erase(separate_if(scheduledLookups.begin(),
974 scheduledLookups.end(),
975 std::back_inserter(postponedLookups),
976 scheduledLookups.begin(),
977 isAlreadyRunning).second,
978 scheduledLookups.end());
979
980 const int availableThreads = std::max(threadPool.maxThreadCount(), 1) - currentLookups.size();
981 if (availableThreads > 0) {
982 int readyToStartCount = qMin(availableThreads, scheduledLookups.size());
983 auto it = scheduledLookups.begin();
984 while (readyToStartCount--) {
985 // runnable now running in new thread, track this in currentLookups
986 threadPool.start(*it);
987 currentLookups.push_back(std::move(*it));
988 ++it;
989 }
990 scheduledLookups.erase(scheduledLookups.begin(), it);
991 }
992#else
993 if (!scheduledLookups.isEmpty())
994 scheduledLookups.takeFirst()->run();
995#endif
996}
997
998// called by QHostInfo
999void QHostInfoLookupManager::scheduleLookup(QHostInfoRunnable *r)
1000{
1001 QMutexLocker locker(&this->mutex);
1002
1003 if (wasDeleted)
1004 return;
1005
1006 scheduledLookups.enqueue(r);
1007 rescheduleWithMutexHeld();
1008}
1009
1010// called by QHostInfo
1011void QHostInfoLookupManager::abortLookup(int id)
1012{
1013 QMutexLocker locker(&this->mutex);
1014
1015 if (wasDeleted)
1016 return;
1017
1018 if (id == -1)
1019 return;
1020
1021#if QT_CONFIG(thread)
1022 // is postponed? delete and return
1023 for (int i = 0; i < postponedLookups.size(); i++) {
1024 if (postponedLookups.at(i)->id == id) {
1025 delete postponedLookups.takeAt(i);
1026 return;
1027 }
1028 }
1029#endif
1030
1031 // is scheduled? delete and return
1032 for (int i = 0; i < scheduledLookups.size(); i++) {
1033 if (scheduledLookups.at(i)->id == id) {
1034 delete scheduledLookups.takeAt(i);
1035 return;
1036 }
1037 }
1038
1039 if (!abortedLookups.contains(id))
1040 abortedLookups.append(id);
1041}
1042
1043// called from QHostInfoRunnable
1044bool QHostInfoLookupManager::wasAborted(int id)
1045{
1046 QMutexLocker locker(&this->mutex);
1047
1048 if (wasDeleted)
1049 return true;
1050
1051 return abortedLookups.contains(id);
1052}
1053
1054// called from QHostInfoRunnable
1055void QHostInfoLookupManager::lookupFinished(QHostInfoRunnable *r)
1056{
1057 QMutexLocker locker(&this->mutex);
1058
1059 if (wasDeleted)
1060 return;
1061
1062#if QT_CONFIG(thread)
1063 currentLookups.removeOne(r);
1064#endif
1065 finishedLookups.append(r);
1066 rescheduleWithMutexHeld();
1067}
1068
1069// This function returns immediately when we had a result in the cache, else it will later emit a signal
1070QHostInfo qt_qhostinfo_lookup(const QString &name, QObject *receiver, const char *member, bool *valid, int *id)
1071{
1072 *valid = false;
1073 *id = -1;
1074
1075 // check cache
1076 QHostInfoLookupManager* manager = theHostInfoLookupManager();
1077 if (manager && manager->cache.isEnabled()) {
1078 QHostInfo info = manager->cache.get(name, valid);
1079 if (*valid) {
1080 return info;
1081 }
1082 }
1083
1084 // was not in cache, trigger lookup
1085 *id = QHostInfo::lookupHostImpl(name, receiver, nullptr, member);
1086
1087 // return empty response, valid==false
1088 return QHostInfo();
1089}
1090
1092{
1093 QHostInfoLookupManager* manager = theHostInfoLookupManager();
1094 if (manager) {
1095 manager->clear();
1096 }
1097}
1098
1099/*!
1100 \fn void QHostInfo::clearCache()
1101 \threadsafe
1102
1103 Clears the internal DNS cache used by lookupHost() and fromName().
1104
1105 Call this when cached host information may be stale and fresh lookups
1106 are needed. Typical use cases include:
1107 \list
1108 \li The application has detected a network configuration change
1109 (e.g. switch between Wi-Fi and Ethernet, or VPN connect/disconnect).
1110 \li A server's address is known to have changed (e.g. dynamic DNS or
1111 failover), and the application should resolve the host name again.
1112 Note that upstream DNS servers and resolvers have their own TTL,
1113 clearing the cache here does not affect them,
1114 so the new lookup may still return the previous address until
1115 the upstream TTL expires.
1116 \li The application uses a cached result for lookups,
1117 but it requires a fresh lookup if the cache has expired or is no longer valid.
1118 \endlist
1119
1120 This function only clears the cache. It does not cancel in-progress
1121 lookups; those will complete and their results will still be delivered.
1122 Use this when you want future lookups to resolve again without
1123 affecting ongoing operations.
1124
1125 \since 6.12
1126 \sa lookupHost(), fromName()
1127*/
1128
1129void QHostInfo::clearCache()
1130{
1131 if (theHostInfoLookupManager.exists()) {
1132 theHostInfoLookupManager->cache.clear();
1133 }
1134}
1135
1136#ifdef QT_BUILD_INTERNAL
1137void Q_AUTOTEST_EXPORT qt_qhostinfo_enable_cache(bool e)
1138{
1139 QHostInfoLookupManager* manager = theHostInfoLookupManager();
1140 if (manager) {
1141 manager->cache.setEnabled(e);
1142 }
1143}
1144
1145void qt_qhostinfo_cache_inject(const QString &hostname, const QHostInfo &resolution)
1146{
1147 QHostInfoLookupManager* manager = theHostInfoLookupManager();
1148 if (!manager || !manager->cache.isEnabled())
1149 return;
1150
1151 manager->cache.put(hostname, resolution);
1152}
1153#endif
1154
1155#if defined(QT_BUILD_INTERNAL) || QT_CONFIG(hostinfocache)
1156QHostInfoCache::QHostInfoCache() : max_age(60), enabled(true), cache(128)
1157{
1158}
1159
1160QHostInfo QHostInfoCache::get(const QString &name, bool *valid)
1161{
1162 QMutexLocker locker(&this->mutex);
1163
1164 *valid = false;
1165 if (QHostInfoCacheElement *element = cache.object(name)) {
1166 if (element->age.elapsed() < max_age*1000)
1167 *valid = true;
1168 return element->info;
1169
1170 // FIXME idea:
1171 // if too old but not expired, trigger a new lookup
1172 // to freshen our cache
1173 }
1174
1175 return QHostInfo();
1176}
1177
1178void QHostInfoCache::put(const QString &name, const QHostInfo &info)
1179{
1180 // if the lookup failed, don't cache
1181 if (info.error() != QHostInfo::NoError)
1182 return;
1183
1184 QHostInfoCacheElement* element = new QHostInfoCacheElement();
1185 element->info = info;
1186 element->age = QElapsedTimer();
1187 element->age.start();
1188
1189 QMutexLocker locker(&this->mutex);
1190 cache.insert(name, element); // cache will take ownership
1191}
1192
1193void QHostInfoCache::clear()
1194{
1195 QMutexLocker locker(&this->mutex);
1196 cache.clear();
1197}
1198#endif // QT_BUILD_INTERNAL || QT_CONFIG(hostinfocache)
1199
1200QT_END_NAMESPACE
1201
1202#include "moc_qhostinfo_p.cpp"
1203#include "moc_qhostinfo.cpp"
void lookupFinished(QHostInfoRunnable *r)
QHostInfoCache cache
The QHostInfo class provides static functions for host name lookups.
Definition qhostinfo.h:22
void qt_qhostinfo_clear_cache()
static int nextId()
QHostInfo qt_qhostinfo_lookup(const QString &name, QObject *receiver, const char *member, bool *valid, int *id)