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

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