1// Copyright (C) 2021 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 DEBUG_LOADING
5
6#include "qnetworkinformation.h"
7#include <QtNetwork/private/qnetworkinformation_p.h>
8#include <QtNetwork/qnetworkinformation.h>
9
10#include <QtCore/private/qobject_p.h>
11#include <QtCore/qcoreapplication.h>
12#include <QtCore/qmutex.h>
13#include <QtCore/qthread.h>
14#include <QtCore/private/qfactoryloader_p.h>
15
16#include <algorithm>
17#include <memory>
18#include <mutex>
19
20QT_BEGIN_NAMESPACE
21Q_DECLARE_LOGGING_CATEGORY(lcNetInfo)
22Q_LOGGING_CATEGORY(lcNetInfo, "qt.network.info");
23
24struct QNetworkInformationDeleter
25{
26 void operator()(QNetworkInformation *information) { delete information; }
27};
28
29Q_GLOBAL_STATIC_WITH_ARGS(QFactoryLoader, qniLoader,
30 (QNetworkInformationBackendFactory_iid,
31 QStringLiteral("/networkinformation")))
32
33struct QStaticNetworkInformationDataHolder
34{
35 QMutex instanceMutex;
36 std::unique_ptr<QNetworkInformation, QNetworkInformationDeleter> instanceHolder;
37 QList<QNetworkInformationBackendFactory *> factories;
38};
39Q_GLOBAL_STATIC(QStaticNetworkInformationDataHolder, dataHolder);
40
41static void networkInfoCleanup()
42{
43 if (!dataHolder.exists())
44 return;
45 QMutexLocker locker(&dataHolder->instanceMutex);
46 QNetworkInformation *instance = dataHolder->instanceHolder.get();
47 if (!instance)
48 return;
49
50 auto needsReinvoke = instance->thread() && instance->thread() != QThread::currentThread();
51 if (needsReinvoke) {
52 QMetaObject::invokeMethod(object: dataHolder->instanceHolder.get(), function: []() { networkInfoCleanup(); });
53 return;
54 }
55 dataHolder->instanceHolder.reset();
56}
57
58class QNetworkInformationPrivate : public QObjectPrivate
59{
60 Q_DECLARE_PUBLIC(QNetworkInformation)
61public:
62 QNetworkInformationPrivate(QNetworkInformationBackend *backend) : backend(backend) {
63 qAddPostRoutine(&networkInfoCleanup);
64 }
65
66 static QNetworkInformation *create(QNetworkInformation::Features features);
67 static QNetworkInformation *create(QStringView name);
68 static QNetworkInformation *instance()
69 {
70 if (!dataHolder())
71 return nullptr;
72 QMutexLocker locker(&dataHolder->instanceMutex);
73 return dataHolder->instanceHolder.get();
74 }
75 static QStringList backendNames();
76 static void addToList(QNetworkInformationBackendFactory *factory);
77 static void removeFromList(QNetworkInformationBackendFactory *factory);
78
79private:
80 static bool initializeList();
81
82 std::unique_ptr<QNetworkInformationBackend> backend;
83};
84
85bool QNetworkInformationPrivate::initializeList()
86{
87 if (!qniLoader())
88 return false;
89 if (!dataHolder())
90 return false;
91 Q_CONSTINIT static QBasicMutex mutex;
92 QMutexLocker initLocker(&mutex);
93
94#if QT_CONFIG(library)
95 qniLoader->update();
96#endif
97 // Instantiates the plugins (and registers the factories)
98 int index = 0;
99 while (qniLoader->instance(index))
100 ++index;
101 initLocker.unlock();
102
103 // Now sort the list on number of features available (then name)
104 const auto featuresNameOrder = [](QNetworkInformationBackendFactory *a,
105 QNetworkInformationBackendFactory *b) {
106 if (!a || !b)
107 return a && !b;
108 auto aFeaturesSupported = qPopulationCount(v: unsigned(a->featuresSupported()));
109 auto bFeaturesSupported = qPopulationCount(v: unsigned(b->featuresSupported()));
110 return aFeaturesSupported > bFeaturesSupported
111 || (aFeaturesSupported == bFeaturesSupported
112 && a->name().compare(s: b->name(), cs: Qt::CaseInsensitive) < 0);
113 };
114 QMutexLocker instanceLocker(&dataHolder->instanceMutex);
115 std::sort(first: dataHolder->factories.begin(), last: dataHolder->factories.end(), comp: featuresNameOrder);
116
117 return !dataHolder->factories.isEmpty();
118}
119
120void QNetworkInformationPrivate::addToList(QNetworkInformationBackendFactory *factory)
121{
122 // @note: factory is in the base class ctor
123 if (!dataHolder())
124 return;
125 QMutexLocker locker(&dataHolder->instanceMutex);
126 dataHolder->factories.append(t: factory);
127}
128
129void QNetworkInformationPrivate::removeFromList(QNetworkInformationBackendFactory *factory)
130{
131 // @note: factory is in the base class dtor
132 if (!dataHolder.exists())
133 return;
134 QMutexLocker locker(&dataHolder->instanceMutex);
135 dataHolder->factories.removeAll(t: factory);
136}
137
138QStringList QNetworkInformationPrivate::backendNames()
139{
140 if (!dataHolder())
141 return {};
142 if (!initializeList())
143 return {};
144
145 QMutexLocker locker(&dataHolder->instanceMutex);
146 const QList copy = dataHolder->factories;
147 locker.unlock();
148
149 QStringList result;
150 result.reserve(asize: copy.size());
151 for (const auto *factory : copy)
152 result << factory->name();
153 return result;
154}
155
156QNetworkInformation *QNetworkInformationPrivate::create(QStringView name)
157{
158 if (name.isEmpty())
159 return nullptr;
160 if (!dataHolder())
161 return nullptr;
162#ifdef DEBUG_LOADING
163 qDebug().nospace() << "create() called with name=\"" << name
164 << "\". instanceHolder initialized? " << !!dataHolder->instanceHolder;
165#endif
166 if (!initializeList()) {
167#ifdef DEBUG_LOADING
168 qDebug("Failed to initialize list, returning.");
169#endif
170 return nullptr;
171 }
172
173 QMutexLocker locker(&dataHolder->instanceMutex);
174 if (dataHolder->instanceHolder)
175 return dataHolder->instanceHolder.get();
176
177
178 const auto nameMatches = [name](QNetworkInformationBackendFactory *factory) {
179 return factory->name().compare(s: name, cs: Qt::CaseInsensitive) == 0;
180 };
181 auto it = std::find_if(first: dataHolder->factories.cbegin(), last: dataHolder->factories.cend(),
182 pred: nameMatches);
183 if (it == dataHolder->factories.cend()) {
184#ifdef DEBUG_LOADING
185 if (dataHolder->factories.isEmpty()) {
186 qDebug("No plugins available");
187 } else {
188 QString listNames;
189 listNames.reserve(8 * dataHolder->factories.count());
190 for (const auto *factory : std::as_const(dataHolder->factories))
191 listNames += factory->name() + QStringLiteral(", ");
192 listNames.chop(2);
193 qDebug().nospace() << "Couldn't find " << name << " in list with names: { "
194 << listNames << " }";
195 }
196#endif
197 return nullptr;
198 }
199#ifdef DEBUG_LOADING
200 qDebug() << "Creating instance using loader named " << (*it)->name();
201#endif
202 QNetworkInformationBackend *backend = (*it)->create(requiredFeatures: (*it)->featuresSupported());
203 if (!backend)
204 return nullptr;
205 dataHolder->instanceHolder.reset(p: new QNetworkInformation(backend));
206 Q_ASSERT(name.isEmpty()
207 || dataHolder->instanceHolder->backendName().compare(name, Qt::CaseInsensitive) == 0);
208 return dataHolder->instanceHolder.get();
209}
210
211QNetworkInformation *QNetworkInformationPrivate::create(QNetworkInformation::Features features)
212{
213 if (!dataHolder())
214 return nullptr;
215#ifdef DEBUG_LOADING
216 qDebug().nospace() << "create() called with features=\"" << features
217 << "\". instanceHolder initialized? " << !!dataHolder->instanceHolder;
218#endif
219 if (features == 0)
220 return nullptr;
221
222 if (!initializeList()) {
223#ifdef DEBUG_LOADING
224 qDebug("Failed to initialize list, returning.");
225#endif
226 return nullptr;
227 }
228 QMutexLocker locker(&dataHolder->instanceMutex);
229 if (dataHolder->instanceHolder)
230 return dataHolder->instanceHolder.get();
231
232 const auto supportsRequestedFeatures = [features](QNetworkInformationBackendFactory *factory) {
233 return factory && (factory->featuresSupported() & features) == features;
234 };
235
236 for (auto it = dataHolder->factories.cbegin(), end = dataHolder->factories.cend(); it != end;
237 ++it) {
238 it = std::find_if(first: it, last: end, pred: supportsRequestedFeatures);
239 if (it == end) {
240#ifdef DEBUG_LOADING
241 if (dataHolder->factories.isEmpty()) {
242 qDebug("No plugins available");
243 } else {
244 QStringList names;
245 names.reserve(dataHolder->factories.count());
246 for (const auto *factory : std::as_const(dataHolder->factories))
247 names += factory->name();
248 qDebug() << "None of the following backends has all the requested features:"
249 << names << features;
250 }
251#endif
252 break;
253 }
254#ifdef DEBUG_LOADING
255 qDebug() << "Creating instance using loader named" << (*it)->name();
256#endif
257 if (QNetworkInformationBackend *backend = (*it)->create(requiredFeatures: features)) {
258 dataHolder->instanceHolder.reset(p: new QNetworkInformation(backend));
259 Q_ASSERT(dataHolder->instanceHolder->supports(features));
260 return dataHolder->instanceHolder.get();
261 }
262#ifdef DEBUG_LOADING
263 else {
264 qDebug() << "The factory returned a nullptr";
265 }
266#endif
267 }
268#ifdef DEBUG_LOADING
269 qDebug() << "Couldn't find/create an appropriate backend.";
270#endif
271 return nullptr;
272}
273
274/*!
275 \class QNetworkInformationBackend
276 \internal (Semi-private)
277 \brief QNetworkInformationBackend provides the interface with
278 which QNetworkInformation does all of its actual work.
279
280 Deriving from and implementing this class makes it a candidate
281 for use with QNetworkInformation. The derived class must, on
282 updates, call setters in the QNetworkInformationBackend which
283 will update the values and emit signals if the value has changed.
284
285 \sa QNetworkInformationBackendFactory
286*/
287
288/*!
289 \internal
290 Destroys base backend class.
291*/
292QNetworkInformationBackend::~QNetworkInformationBackend() = default;
293
294/*!
295 \fn QNetworkInformationBackend::name()
296
297 Backend name, return the same in
298 QNetworkInformationBackendFactory::name().
299*/
300
301/*!
302 \fn QNetworkInformation::Features QNetworkInformationBackend::featuresSupported()
303
304 Features supported, return the same in
305 QNetworkInformationBackendFactory::featuresSupported().
306*/
307
308/*!
309 \fn void QNetworkInformationBackend::reachabilityChanged()
310
311 You should not emit this signal manually, call setReachability()
312 instead which will emit this signal when the value changes.
313
314 \sa setReachability
315*/
316
317/*!
318 \fn void QNetworkInformationBackend::setReachability(QNetworkInformation::Reachability reachability)
319
320 Call this when reachability has changed. It will automatically
321 emit reachabilityChanged().
322
323 \sa setReachability
324*/
325
326/*!
327 \class QNetworkInformationBackendFactory
328 \internal (Semi-private)
329 \brief QNetworkInformationBackendFactory provides the interface
330 for creating instances of QNetworkInformationBackend.
331
332 Deriving from and implementing this class will let you register
333 your plugin with QNetworkInformation. It must provide some basic
334 information for querying information about the backend, and must
335 also create the backend if requested. If some pre-conditions for
336 the backend is not met it must return \nullptr.
337*/
338
339/*!
340 \internal
341 Adds the factory to an internal list.
342*/
343QNetworkInformationBackendFactory::QNetworkInformationBackendFactory()
344{
345 QNetworkInformationPrivate::addToList(factory: this);
346}
347
348/*!
349 \internal
350 Removes the factory from an internal list.
351*/
352QNetworkInformationBackendFactory::~QNetworkInformationBackendFactory()
353{
354 QNetworkInformationPrivate::removeFromList(factory: this);
355}
356
357/*!
358 \fn QString QNetworkInformationBackendFactory::name()
359
360 Backend name, return the same in
361 QNetworkInformationBackend::name().
362*/
363
364/*!
365 \fn QNetworkInformation::Features QNetworkInformationBackendFactory::featuresSupported()
366
367 Features supported, return the same in
368 QNetworkInformationBackend::featuresSupported().
369 The factory should not promise support for features that wouldn't
370 be available after creating the backend.
371*/
372
373/*!
374 \fn QNetworkInformationBackend *QNetworkInformationBackendFactory::create()
375
376 Create and return an instance of QNetworkInformationBackend. It
377 will be deallocated by QNetworkInformation on shutdown. If some
378 precondition is not met, meaning the backend would not function
379 correctly, then you must return \nullptr.
380*/
381
382/*!
383 \class QNetworkInformation
384 \inmodule QtNetwork
385 \since 6.1
386 \brief QNetworkInformation exposes various network information
387 through native backends.
388
389 QNetworkInformation provides a cross-platform interface to
390 network-related information through plugins.
391
392 Various plugins can have various functionality supported, and so
393 you can load() plugins based on which features are needed.
394
395 QNetworkInformation is a singleton and stays alive from the first
396 successful load() until destruction of the QCoreApplication object.
397 If you destroy and re-create the QCoreApplication object you must call
398 load() again.
399
400 \sa QNetworkInformation::Feature
401*/
402
403/*!
404 \enum QNetworkInformation::Feature
405
406 Lists all of the features that a plugin may currently support.
407 This can be used in QNetworkInformation::load().
408
409 \value Reachability
410 If the plugin supports this feature then the \c reachability property
411 will provide useful results. Otherwise it will always return
412 \c{Reachability::Unknown}.
413 See also QNetworkInformation::Reachability.
414
415 \value CaptivePortal
416 If the plugin supports this feature then the \c isBehindCaptivePortal
417 property will provide useful results. Otherwise it will always return
418 \c{false}.
419
420 \value TransportMedium
421 If the plugin supports this feature then the \c transportMedium
422 property will provide useful results. Otherwise it will always return
423 \c{TransportMedium::Unknown}.
424 See also QNetworkInformation::TransportMedium.
425
426 \value Metered
427 If the plugin supports this feature then the \c isMetered
428 property will provide useful results. Otherwise it will always return
429 \c{false}.
430*/
431
432/*!
433 \enum QNetworkInformation::Reachability
434
435 \value Unknown
436 If this value is returned then we may be connected but the OS
437 has still not confirmed full connectivity, or this feature
438 is not supported.
439 \value Disconnected
440 Indicates that the system may have no connectivity at all.
441 \value Local
442 Indicates that the system is connected to a network, but it
443 might only be able to access devices on the local network.
444 \value Site
445 Indicates that the system is connected to a network, but it
446 might only be able to access devices on the local subnet or an
447 intranet.
448 \value Online
449 Indicates that the system is connected to a network and
450 able to access the Internet.
451
452 \sa QNetworkInformation::reachability
453*/
454
455/*!
456 \enum QNetworkInformation::TransportMedium
457 \since 6.3
458
459 Lists the currently recognized media with which one can connect to the
460 internet.
461
462 \value Unknown
463 Returned if either the OS reports no active medium, the active medium is
464 not recognized by Qt, or the TransportMedium feature is not supported.
465 \value Ethernet
466 Indicates that the currently active connection is using ethernet.
467 Note: This value may also be returned when Windows is connected to a
468 Bluetooth personal area network.
469 \value Cellular
470 Indicates that the currently active connection is using a cellular
471 network.
472 \value WiFi
473 Indicates that the currently active connection is using Wi-Fi.
474 \value Bluetooth
475 Indicates that the currently active connection is connected using
476 Bluetooth.
477
478 \sa QNetworkInformation::transportMedium
479*/
480
481/*!
482 \internal ctor
483*/
484QNetworkInformation::QNetworkInformation(QNetworkInformationBackend *backend)
485 : QObject(*(new QNetworkInformationPrivate(backend)))
486{
487 connect(sender: backend, signal: &QNetworkInformationBackend::reachabilityChanged, context: this,
488 slot: &QNetworkInformation::reachabilityChanged);
489 connect(sender: backend, signal: &QNetworkInformationBackend::behindCaptivePortalChanged, context: this,
490 slot: &QNetworkInformation::isBehindCaptivePortalChanged);
491 connect(sender: backend, signal: &QNetworkInformationBackend::transportMediumChanged, context: this,
492 slot: &QNetworkInformation::transportMediumChanged);
493 connect(sender: backend, signal: &QNetworkInformationBackend::isMeteredChanged, context: this,
494 slot: &QNetworkInformation::isMeteredChanged);
495}
496
497/*!
498 \internal dtor
499*/
500QNetworkInformation::~QNetworkInformation() = default;
501
502/*!
503 \property QNetworkInformation::reachability
504 \brief The current state of the system's network connectivity.
505
506 Indicates the level of connectivity that can be expected. Do note
507 that this is only based on what the plugin/operating system
508 reports. In certain scenarios this is known to be wrong. For
509 example, on Windows the 'Online' check, by default, is performed
510 by Windows connecting to a Microsoft-owned server. If this server
511 is for any reason blocked then it will assume it does not have
512 Online reachability. Because of this you should not use this as a
513 pre-check before attempting to make a connection.
514*/
515
516QNetworkInformation::Reachability QNetworkInformation::reachability() const
517{
518 return d_func()->backend->reachability();
519}
520
521/*!
522 \property QNetworkInformation::isBehindCaptivePortal
523 \brief Lets you know if the user's device is behind a captive portal.
524 \since 6.2
525
526 This property indicates if the user's device is currently known to be
527 behind a captive portal. This functionality relies on the operating system's
528 detection of captive portals and is not supported on systems that don't
529 report this. On systems where this is not supported this will always return
530 \c{false}.
531*/
532bool QNetworkInformation::isBehindCaptivePortal() const
533{
534 return d_func()->backend->behindCaptivePortal();
535}
536
537/*!
538 \property QNetworkInformation::transportMedium
539 \brief The currently active transport medium for the application
540 \since 6.3
541
542 This property returns the currently active transport medium for the
543 application, on operating systems where such information is available.
544
545 When the current transport medium changes a signal is emitted, this can,
546 for instance, occur when a user leaves the range of a WiFi network, unplugs
547 their ethernet cable or enables Airplane mode.
548*/
549QNetworkInformation::TransportMedium QNetworkInformation::transportMedium() const
550{
551 return d_func()->backend->transportMedium();
552}
553
554/*!
555 \property QNetworkInformation::isMetered
556 \brief Check if the current connection is metered
557 \since 6.3
558
559 This property returns whether the current connection is (known to be)
560 metered or not. You can use this as a guiding factor to decide whether your
561 application should perform certain network requests or uploads.
562 For instance, you may not want to upload logs or diagnostics while this
563 property is \c true.
564*/
565bool QNetworkInformation::isMetered() const
566{
567 return d_func()->backend->isMetered();
568}
569
570/*!
571 Returns the name of the currently loaded backend.
572*/
573QString QNetworkInformation::backendName() const
574{
575 return d_func()->backend->name();
576}
577
578/*!
579 Returns \c true if the currently loaded backend supports
580 \a features.
581*/
582bool QNetworkInformation::supports(Features features) const
583{
584 return (d_func()->backend->featuresSupported() & features) == features;
585}
586
587/*!
588 \since 6.3
589
590 Returns all the supported features of the current backend.
591*/
592QNetworkInformation::Features QNetworkInformation::supportedFeatures() const
593{
594 return d_func()->backend->featuresSupported();
595}
596
597/*!
598 \since 6.3
599
600 Attempts to load the platform-default backend.
601
602 This platform-to-plugin mapping is as follows:
603
604 \table
605 \header
606 \li Platform
607 \li Plugin-name
608 \row
609 \li Windows
610 \li networklistmanager
611 \row
612 \li Apple (macOS/iOS)
613 \li scnetworkreachability
614 \row
615 \li Android
616 \li android
617 \row
618 \li Linux
619 \li networkmanager
620 \endtable
621
622 This function is provided for convenience where the default for a given
623 platform is good enough. If you are not using the default plugins you must
624 use one of the other load() overloads.
625
626 Returns \c true if it managed to load the backend or if it was already
627 loaded. Returns \c false otherwise.
628
629 \sa instance(), load()
630*/
631bool QNetworkInformation::loadDefaultBackend()
632{
633 int index = -1;
634#ifdef Q_OS_WIN
635 index = QNetworkInformationBackend::PluginNamesWindowsIndex;
636#elif defined(Q_OS_DARWIN)
637 index = QNetworkInformationBackend::PluginNamesAppleIndex;
638#elif defined(Q_OS_ANDROID)
639 index = QNetworkInformationBackend::PluginNamesAndroidIndex;
640#elif defined(Q_OS_LINUX)
641 index = QNetworkInformationBackend::PluginNamesLinuxIndex;
642#endif
643 if (index == -1)
644 return false;
645 return loadBackendByName(backend: QNetworkInformationBackend::PluginNames[index]);
646}
647
648/*!
649 \since 6.4
650
651 Attempts to load a backend whose name matches \a backend
652 (case insensitively).
653
654 Returns \c true if it managed to load the requested backend or
655 if it was already loaded. Returns \c false otherwise.
656
657 \sa instance
658*/
659bool QNetworkInformation::loadBackendByName(QStringView backend)
660{
661 auto loadedBackend = QNetworkInformationPrivate::create(name: backend);
662 return loadedBackend && loadedBackend->backendName().compare(s: backend, cs: Qt::CaseInsensitive) == 0;
663}
664
665#if QT_DEPRECATED_SINCE(6,4)
666/*!
667 \deprecated [6.4] Use loadBackendByName() instead.
668
669 \sa loadBackendByName(), loadDefaultBackend(), loadBackendByFeatures()
670*/
671bool QNetworkInformation::load(QStringView backend)
672{
673 return loadBackendByName(backend);
674}
675#endif // QT_DEPRECATED_SINCE(6,4)
676
677/*!
678 \since 6.4
679 Load a backend which supports \a features.
680
681 Returns \c true if it managed to load the requested backend or
682 if it was already loaded. Returns \c false otherwise.
683
684 \sa instance
685*/
686bool QNetworkInformation::loadBackendByFeatures(Features features)
687{
688 auto loadedBackend = QNetworkInformationPrivate::create(features);
689 return loadedBackend && loadedBackend->supports(features);
690}
691
692#if QT_DEPRECATED_SINCE(6,4)
693/*!
694 \deprecated [6.4] Use loadBackendByFeatures() instead.
695
696 \sa loadBackendByName(), loadDefaultBackend(), loadBackendByFeatures()
697*/
698bool QNetworkInformation::load(Features features)
699{
700 return loadBackendByFeatures(features);
701}
702#endif // QT_DEPRECATED_SINCE(6,4)
703
704/*!
705 Returns a list of the names of all currently available backends.
706*/
707QStringList QNetworkInformation::availableBackends()
708{
709 return QNetworkInformationPrivate::backendNames();
710}
711
712/*!
713 Returns a pointer to the instance of the QNetworkInformation,
714 if any.
715
716 \sa load()
717*/
718QNetworkInformation *QNetworkInformation::instance()
719{
720 return QNetworkInformationPrivate::instance();
721}
722
723QT_END_NAMESPACE
724
725#include "moc_qnetworkinformation.cpp"
726#include "moc_qnetworkinformation_p.cpp"
727

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