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

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

source code of qtbase/src/network/kernel/qhostinfo.cpp