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

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