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

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

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