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