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