1 /****************************************************************************
2 **
3 ** Copyright (C) 2019 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 #include "qnetconmonitor_p.h"
41 
42 #include "private/qobject_p.h"
43 
44 #include <QtCore/quuid.h>
45 #include <QtCore/qmetaobject.h>
46 
47 #include <QtNetwork/qnetworkinterface.h>
48 
49 #include <objbase.h>
50 #include <netlistmgr.h>
51 #include <wrl/client.h>
52 #include <wrl/wrappers/corewrappers.h>
53 #include <comdef.h>
54 #include <iphlpapi.h>
55 
56 #include <algorithm>
57 
58 using namespace Microsoft::WRL;
59 
60 QT_BEGIN_NAMESPACE
61 
62 Q_LOGGING_CATEGORY(lcNetMon, "qt.network.monitor");
63 
64 namespace {
errorStringFromHResult(HRESULT hr)65 QString errorStringFromHResult(HRESULT hr)
66 {
67     _com_error error(hr);
68     return QString::fromWCharArray(error.ErrorMessage());
69 }
70 
71 template<typename T>
QueryInterfaceImpl(IUnknown * from,REFIID riid,void ** ppvObject)72 bool QueryInterfaceImpl(IUnknown *from, REFIID riid, void **ppvObject)
73 {
74     if (riid == __uuidof(T)) {
75         *ppvObject = static_cast<T *>(from);
76         from->AddRef();
77         return true;
78     }
79     return false;
80 }
81 
getInterfaceFromHostAddress(const QHostAddress & local)82 QNetworkInterface getInterfaceFromHostAddress(const QHostAddress &local)
83 {
84     QList<QNetworkInterface> interfaces = QNetworkInterface::allInterfaces();
85     auto it = std::find_if(
86             interfaces.cbegin(), interfaces.cend(), [&local](const QNetworkInterface &iface) {
87                 const auto &entries = iface.addressEntries();
88                 return std::any_of(entries.cbegin(), entries.cend(),
89                                    [&local](const QNetworkAddressEntry &entry) {
90                                        return entry.ip().isEqual(local,
91                                                                  QHostAddress::TolerantConversion);
92                                    });
93             });
94     if (it == interfaces.cend()) {
95         qCWarning(lcNetMon, "Could not find the interface for the local address.");
96         return {};
97     }
98     return *it;
99 }
100 } // anonymous namespace
101 
102 class QNetworkConnectionEvents : public INetworkConnectionEvents
103 {
104 public:
105     QNetworkConnectionEvents(QNetworkConnectionMonitorPrivate *monitor);
106     virtual ~QNetworkConnectionEvents();
107 
108     HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObject) override;
109 
AddRef()110     ULONG STDMETHODCALLTYPE AddRef() override { return ++ref; }
Release()111     ULONG STDMETHODCALLTYPE Release() override
112     {
113         if (--ref == 0) {
114             delete this;
115             return 0;
116         }
117         return ref;
118     }
119 
120     HRESULT STDMETHODCALLTYPE
121     NetworkConnectionConnectivityChanged(GUID connectionId, NLM_CONNECTIVITY connectivity) override;
122     HRESULT STDMETHODCALLTYPE NetworkConnectionPropertyChanged(
123             GUID connectionId, NLM_CONNECTION_PROPERTY_CHANGE flags) override;
124 
125     Q_REQUIRED_RESULT
126     bool setTarget(const QNetworkInterface &iface);
127     Q_REQUIRED_RESULT
128     bool startMonitoring();
129     Q_REQUIRED_RESULT
130     bool stopMonitoring();
131 
132 private:
133     ComPtr<INetworkConnection> getNetworkConnectionFromAdapterGuid(QUuid guid);
134 
135     QUuid currentConnectionId{};
136 
137     ComPtr<INetworkListManager> networkListManager;
138     ComPtr<IConnectionPoint> connectionPoint;
139 
140     QNetworkConnectionMonitorPrivate *monitor = nullptr;
141 
142     QAtomicInteger<ULONG> ref = 0;
143     DWORD cookie = 0;
144 };
145 
146 class QNetworkConnectionMonitorPrivate : public QObjectPrivate
147 {
148     Q_DECLARE_PUBLIC(QNetworkConnectionMonitor);
149 
150 public:
151     QNetworkConnectionMonitorPrivate();
152     ~QNetworkConnectionMonitorPrivate();
153 
154     Q_REQUIRED_RESULT
155     bool setTargets(const QHostAddress &local, const QHostAddress &remote);
156     Q_REQUIRED_RESULT
157     bool startMonitoring();
158     void stopMonitoring();
159 
160     void setConnectivity(NLM_CONNECTIVITY newConnectivity);
161 
162 private:
163     ComPtr<QNetworkConnectionEvents> connectionEvents;
164     // We can assume we have access to internet/subnet when this class is created because
165     // connection has already been established to the peer:
166     NLM_CONNECTIVITY connectivity = NLM_CONNECTIVITY(
167             NLM_CONNECTIVITY_IPV4_INTERNET | NLM_CONNECTIVITY_IPV6_INTERNET
168             | NLM_CONNECTIVITY_IPV4_SUBNET | NLM_CONNECTIVITY_IPV6_SUBNET
169             | NLM_CONNECTIVITY_IPV4_LOCALNETWORK | NLM_CONNECTIVITY_IPV6_LOCALNETWORK
170             | NLM_CONNECTIVITY_IPV4_NOTRAFFIC | NLM_CONNECTIVITY_IPV6_NOTRAFFIC);
171 
172     bool sameSubnet = false;
173     bool isLinkLocal = false;
174     bool monitoring = false;
175     bool comInitFailed = false;
176     bool remoteIsIPv6 = false;
177 };
178 
QNetworkConnectionEvents(QNetworkConnectionMonitorPrivate * monitor)179 QNetworkConnectionEvents::QNetworkConnectionEvents(QNetworkConnectionMonitorPrivate *monitor)
180     : monitor(monitor)
181 {
182     auto hr = CoCreateInstance(CLSID_NetworkListManager, nullptr, CLSCTX_INPROC_SERVER,
183                                IID_INetworkListManager, &networkListManager);
184     if (FAILED(hr)) {
185         qCWarning(lcNetMon) << "Could not get a NetworkListManager instance:"
186                             << errorStringFromHResult(hr);
187         return;
188     }
189 
190     ComPtr<IConnectionPointContainer> connectionPointContainer;
191     hr = networkListManager.As(&connectionPointContainer);
192     if (SUCCEEDED(hr)) {
193         hr = connectionPointContainer->FindConnectionPoint(IID_INetworkConnectionEvents,
194                                                            &connectionPoint);
195     }
196     if (FAILED(hr)) {
197         qCWarning(lcNetMon) << "Failed to get connection point for network events:"
198                             << errorStringFromHResult(hr);
199     }
200 }
201 
~QNetworkConnectionEvents()202 QNetworkConnectionEvents::~QNetworkConnectionEvents()
203 {
204     Q_ASSERT(ref == 0);
205 }
206 
getNetworkConnectionFromAdapterGuid(QUuid guid)207 ComPtr<INetworkConnection> QNetworkConnectionEvents::getNetworkConnectionFromAdapterGuid(QUuid guid)
208 {
209     ComPtr<IEnumNetworkConnections> connections;
210     auto hr = networkListManager->GetNetworkConnections(connections.GetAddressOf());
211     if (FAILED(hr)) {
212         qCWarning(lcNetMon) << "Failed to enumerate network connections:"
213                             << errorStringFromHResult(hr);
214         return nullptr;
215     }
216     ComPtr<INetworkConnection> connection = nullptr;
217     do {
218         hr = connections->Next(1, connection.GetAddressOf(), nullptr);
219         if (FAILED(hr)) {
220             qCWarning(lcNetMon) << "Failed to get next network connection in enumeration:"
221                                 << errorStringFromHResult(hr);
222             break;
223         }
224         if (connection) {
225             GUID adapterId;
226             hr = connection->GetAdapterId(&adapterId);
227             if (FAILED(hr)) {
228                 qCWarning(lcNetMon) << "Failed to get adapter ID from network connection:"
229                                     << errorStringFromHResult(hr);
230                 continue;
231             }
232             if (guid == adapterId)
233                 return connection;
234         }
235     } while (connection);
236     return nullptr;
237 }
238 
QueryInterface(REFIID riid,void ** ppvObject)239 HRESULT STDMETHODCALLTYPE QNetworkConnectionEvents::QueryInterface(REFIID riid, void **ppvObject)
240 {
241     if (!ppvObject)
242         return E_INVALIDARG;
243 
244     return QueryInterfaceImpl<IUnknown>(this, riid, ppvObject)
245                     || QueryInterfaceImpl<INetworkConnectionEvents>(this, riid, ppvObject)
246             ? S_OK
247             : E_NOINTERFACE;
248 }
249 
NetworkConnectionConnectivityChanged(GUID connectionId,NLM_CONNECTIVITY newConnectivity)250 HRESULT STDMETHODCALLTYPE QNetworkConnectionEvents::NetworkConnectionConnectivityChanged(
251         GUID connectionId, NLM_CONNECTIVITY newConnectivity)
252 {
253     // This function is run on a different thread than 'monitor' is created on, so we need to run
254     // it on that thread
255     QMetaObject::invokeMethod(monitor->q_ptr,
256                               [this, connectionId, newConnectivity, monitor = this->monitor]() {
257                                   if (connectionId == currentConnectionId)
258                                       monitor->setConnectivity(newConnectivity);
259                               },
260                               Qt::QueuedConnection);
261     return S_OK;
262 }
263 
NetworkConnectionPropertyChanged(GUID connectionId,NLM_CONNECTION_PROPERTY_CHANGE flags)264 HRESULT STDMETHODCALLTYPE QNetworkConnectionEvents::NetworkConnectionPropertyChanged(
265         GUID connectionId, NLM_CONNECTION_PROPERTY_CHANGE flags)
266 {
267     Q_UNUSED(connectionId);
268     Q_UNUSED(flags);
269     return E_NOTIMPL;
270 }
271 
setTarget(const QNetworkInterface & iface)272 bool QNetworkConnectionEvents::setTarget(const QNetworkInterface &iface)
273 {
274     // Unset this in case it's already set to something
275     currentConnectionId = QUuid{};
276 
277     NET_LUID luid;
278     if (ConvertInterfaceIndexToLuid(iface.index(), &luid) != NO_ERROR) {
279         qCWarning(lcNetMon, "Could not get the LUID for the interface.");
280         return false;
281     }
282     GUID guid;
283     if (ConvertInterfaceLuidToGuid(&luid, &guid) != NO_ERROR) {
284         qCWarning(lcNetMon, "Could not get the GUID for the interface.");
285         return false;
286     }
287     ComPtr<INetworkConnection> connection = getNetworkConnectionFromAdapterGuid(guid);
288     if (!connection) {
289         qCWarning(lcNetMon, "Could not get the INetworkConnection instance for the adapter GUID.");
290         return false;
291     }
292     auto hr = connection->GetConnectionId(&guid);
293     if (FAILED(hr)) {
294         qCWarning(lcNetMon) << "Failed to get the connection's GUID:" << errorStringFromHResult(hr);
295         return false;
296     }
297     currentConnectionId = guid;
298 
299     return true;
300 }
301 
startMonitoring()302 bool QNetworkConnectionEvents::startMonitoring()
303 {
304     if (currentConnectionId.isNull()) {
305         qCWarning(lcNetMon, "Can not start monitoring, set targets first");
306         return false;
307     }
308     if (!connectionPoint) {
309         qCWarning(lcNetMon,
310                   "We don't have the connection point, cannot start listening to events!");
311         return false;
312     }
313 
314     auto hr = connectionPoint->Advise(this, &cookie);
315     if (FAILED(hr)) {
316         qCWarning(lcNetMon) << "Failed to subscribe to network connectivity events:"
317                             << errorStringFromHResult(hr);
318         return false;
319     }
320     return true;
321 }
322 
stopMonitoring()323 bool QNetworkConnectionEvents::stopMonitoring()
324 {
325     auto hr = connectionPoint->Unadvise(cookie);
326     if (FAILED(hr)) {
327         qCWarning(lcNetMon) << "Failed to unsubscribe from network connection events:"
328                             << errorStringFromHResult(hr);
329         return false;
330     }
331     cookie = 0;
332     currentConnectionId = QUuid{};
333     return true;
334 }
335 
QNetworkConnectionMonitorPrivate()336 QNetworkConnectionMonitorPrivate::QNetworkConnectionMonitorPrivate()
337 {
338     auto hr = CoInitialize(nullptr);
339     if (FAILED(hr)) {
340         qCWarning(lcNetMon) << "Failed to initialize COM:" << errorStringFromHResult(hr);
341         comInitFailed = true;
342         return;
343     }
344 
345     connectionEvents = new QNetworkConnectionEvents(this);
346 }
347 
~QNetworkConnectionMonitorPrivate()348 QNetworkConnectionMonitorPrivate::~QNetworkConnectionMonitorPrivate()
349 {
350     if (comInitFailed)
351         return;
352     if (monitoring)
353         stopMonitoring();
354     connectionEvents.Reset();
355     CoUninitialize();
356 }
357 
setTargets(const QHostAddress & local,const QHostAddress & remote)358 bool QNetworkConnectionMonitorPrivate::setTargets(const QHostAddress &local,
359                                                   const QHostAddress &remote)
360 {
361     if (comInitFailed)
362         return false;
363 
364     QNetworkInterface iface = getInterfaceFromHostAddress(local);
365     if (!iface.isValid())
366         return false;
367     const auto &addressEntries = iface.addressEntries();
368     auto it = std::find_if(
369             addressEntries.cbegin(), addressEntries.cend(),
370             [&local](const QNetworkAddressEntry &entry) { return entry.ip() == local; });
371     if (Q_UNLIKELY(it == addressEntries.cend())) {
372         qCWarning(lcNetMon, "The address entry we were working with disappeared");
373         return false;
374     }
375     sameSubnet = remote.isInSubnet(local, it->prefixLength());
376     isLinkLocal = remote.isLinkLocal() && local.isLinkLocal();
377     remoteIsIPv6 = remote.protocol() == QAbstractSocket::IPv6Protocol;
378 
379     return connectionEvents->setTarget(iface);
380 }
381 
setConnectivity(NLM_CONNECTIVITY newConnectivity)382 void QNetworkConnectionMonitorPrivate::setConnectivity(NLM_CONNECTIVITY newConnectivity)
383 {
384     Q_Q(QNetworkConnectionMonitor);
385     const bool reachable = q->isReachable();
386     connectivity = newConnectivity;
387     const bool newReachable = q->isReachable();
388     if (reachable != newReachable)
389         emit q->reachabilityChanged(newReachable);
390 }
391 
startMonitoring()392 bool QNetworkConnectionMonitorPrivate::startMonitoring()
393 {
394     Q_ASSERT(connectionEvents);
395     Q_ASSERT(!monitoring);
396     if (connectionEvents->startMonitoring())
397         monitoring = true;
398     return monitoring;
399 }
400 
stopMonitoring()401 void QNetworkConnectionMonitorPrivate::stopMonitoring()
402 {
403     Q_ASSERT(connectionEvents);
404     Q_ASSERT(monitoring);
405     if (connectionEvents->stopMonitoring())
406         monitoring = false;
407 }
408 
QNetworkConnectionMonitor()409 QNetworkConnectionMonitor::QNetworkConnectionMonitor()
410     : QObject(*new QNetworkConnectionMonitorPrivate)
411 {
412 }
413 
QNetworkConnectionMonitor(const QHostAddress & local,const QHostAddress & remote)414 QNetworkConnectionMonitor::QNetworkConnectionMonitor(const QHostAddress &local,
415                                                      const QHostAddress &remote)
416     : QObject(*new QNetworkConnectionMonitorPrivate)
417 {
418     setTargets(local, remote);
419 }
420 
421 QNetworkConnectionMonitor::~QNetworkConnectionMonitor() = default;
422 
setTargets(const QHostAddress & local,const QHostAddress & remote)423 bool QNetworkConnectionMonitor::setTargets(const QHostAddress &local, const QHostAddress &remote)
424 {
425     if (isMonitoring()) {
426         qCWarning(lcNetMon, "Monitor is already active, call stopMonitoring() first");
427         return false;
428     }
429     if (local.isNull()) {
430         qCWarning(lcNetMon, "Invalid (null) local address, cannot create a reachability target");
431         return false;
432     }
433     // Silently return false for loopback addresses instead of printing warnings later
434     if (remote.isLoopback())
435         return false;
436 
437     return d_func()->setTargets(local, remote);
438 }
439 
startMonitoring()440 bool QNetworkConnectionMonitor::startMonitoring()
441 {
442     Q_D(QNetworkConnectionMonitor);
443     if (isMonitoring()) {
444         qCWarning(lcNetMon, "Monitor is already active, call stopMonitoring() first");
445         return false;
446     }
447     return d->startMonitoring();
448 }
449 
isMonitoring() const450 bool QNetworkConnectionMonitor::isMonitoring() const
451 {
452     return d_func()->monitoring;
453 }
454 
stopMonitoring()455 void QNetworkConnectionMonitor::stopMonitoring()
456 {
457     Q_D(QNetworkConnectionMonitor);
458     if (!isMonitoring()) {
459         qCWarning(lcNetMon, "stopMonitoring was called when not monitoring!");
460         return;
461     }
462     d->stopMonitoring();
463 }
464 
isReachable()465 bool QNetworkConnectionMonitor::isReachable()
466 {
467     Q_D(QNetworkConnectionMonitor);
468 
469     const NLM_CONNECTIVITY RequiredSameSubnetIPv6 =
470             NLM_CONNECTIVITY(NLM_CONNECTIVITY_IPV6_SUBNET | NLM_CONNECTIVITY_IPV6_LOCALNETWORK
471                              | NLM_CONNECTIVITY_IPV6_INTERNET);
472     const NLM_CONNECTIVITY RequiredSameSubnetIPv4 =
473             NLM_CONNECTIVITY(NLM_CONNECTIVITY_IPV4_SUBNET | NLM_CONNECTIVITY_IPV4_LOCALNETWORK
474                              | NLM_CONNECTIVITY_IPV4_INTERNET);
475 
476     NLM_CONNECTIVITY required;
477     if (d->isLinkLocal) {
478         required = NLM_CONNECTIVITY(
479                 d->remoteIsIPv6 ? NLM_CONNECTIVITY_IPV6_NOTRAFFIC | RequiredSameSubnetIPv6
480                                 : NLM_CONNECTIVITY_IPV4_NOTRAFFIC | RequiredSameSubnetIPv4);
481     } else if (d->sameSubnet) {
482         required =
483                 NLM_CONNECTIVITY(d->remoteIsIPv6 ? RequiredSameSubnetIPv6 : RequiredSameSubnetIPv4);
484 
485     } else {
486         required = NLM_CONNECTIVITY(d->remoteIsIPv6 ? NLM_CONNECTIVITY_IPV6_INTERNET
487                                                     : NLM_CONNECTIVITY_IPV4_INTERNET);
488     }
489 
490     return d_func()->connectivity & required;
491 }
492 
493 class QNetworkListManagerEvents : public INetworkListManagerEvents
494 {
495 public:
496     QNetworkListManagerEvents(QNetworkStatusMonitorPrivate *monitor);
497     virtual ~QNetworkListManagerEvents();
498 
499     HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObject) override;
500 
AddRef()501     ULONG STDMETHODCALLTYPE AddRef() override { return ++ref; }
Release()502     ULONG STDMETHODCALLTYPE Release() override
503     {
504         if (--ref == 0) {
505             delete this;
506             return 0;
507         }
508         return ref;
509     }
510 
511     HRESULT STDMETHODCALLTYPE ConnectivityChanged(NLM_CONNECTIVITY newConnectivity) override;
512 
513     Q_REQUIRED_RESULT
514     bool start();
515     bool stop();
516 
517 private:
518     ComPtr<INetworkListManager> networkListManager = nullptr;
519     ComPtr<IConnectionPoint> connectionPoint = nullptr;
520 
521     QNetworkStatusMonitorPrivate *monitor = nullptr;
522 
523     QAtomicInteger<ULONG> ref = 0;
524     DWORD cookie = 0;
525 };
526 
527 class QNetworkStatusMonitorPrivate : public QObjectPrivate
528 {
529     Q_DECLARE_PUBLIC(QNetworkStatusMonitor);
530 
531 public:
532     QNetworkStatusMonitorPrivate();
533     ~QNetworkStatusMonitorPrivate();
534 
535     Q_REQUIRED_RESULT
536     bool start();
537     void stop();
538 
539     void setConnectivity(NLM_CONNECTIVITY newConnectivity);
540 
541 private:
542     friend class QNetworkListManagerEvents;
543 
544     ComPtr<QNetworkListManagerEvents> managerEvents;
545     NLM_CONNECTIVITY connectivity = NLM_CONNECTIVITY_DISCONNECTED;
546 
547     bool monitoring = false;
548     bool comInitFailed = false;
549 };
550 
QNetworkListManagerEvents(QNetworkStatusMonitorPrivate * monitor)551 QNetworkListManagerEvents::QNetworkListManagerEvents(QNetworkStatusMonitorPrivate *monitor)
552     : monitor(monitor)
553 {
554     auto hr = CoCreateInstance(CLSID_NetworkListManager, nullptr, CLSCTX_INPROC_SERVER,
555                                IID_INetworkListManager, &networkListManager);
556     if (FAILED(hr)) {
557         qCWarning(lcNetMon) << "Could not get a NetworkListManager instance:"
558                             << errorStringFromHResult(hr);
559         return;
560     }
561 
562     // Set initial connectivity
563     hr = networkListManager->GetConnectivity(&monitor->connectivity);
564     if (FAILED(hr))
565         qCWarning(lcNetMon) << "Could not get connectivity:" << errorStringFromHResult(hr);
566 
567     ComPtr<IConnectionPointContainer> connectionPointContainer;
568     hr = networkListManager.As(&connectionPointContainer);
569     if (SUCCEEDED(hr)) {
570         hr = connectionPointContainer->FindConnectionPoint(IID_INetworkListManagerEvents,
571                                                            &connectionPoint);
572     }
573     if (FAILED(hr)) {
574         qCWarning(lcNetMon) << "Failed to get connection point for network list manager events:"
575                             << errorStringFromHResult(hr);
576     }
577 }
578 
~QNetworkListManagerEvents()579 QNetworkListManagerEvents::~QNetworkListManagerEvents()
580 {
581     Q_ASSERT(ref == 0);
582 }
583 
QueryInterface(REFIID riid,void ** ppvObject)584 HRESULT STDMETHODCALLTYPE QNetworkListManagerEvents::QueryInterface(REFIID riid, void **ppvObject)
585 {
586     if (!ppvObject)
587         return E_INVALIDARG;
588 
589     return QueryInterfaceImpl<IUnknown>(this, riid, ppvObject)
590                     || QueryInterfaceImpl<INetworkListManagerEvents>(this, riid, ppvObject)
591             ? S_OK
592             : E_NOINTERFACE;
593 }
594 
595 HRESULT STDMETHODCALLTYPE
ConnectivityChanged(NLM_CONNECTIVITY newConnectivity)596 QNetworkListManagerEvents::ConnectivityChanged(NLM_CONNECTIVITY newConnectivity)
597 {
598     // This function is run on a different thread than 'monitor' is created on, so we need to run
599     // it on that thread
600     QMetaObject::invokeMethod(monitor->q_ptr,
601                               [newConnectivity, monitor = this->monitor]() {
602                                   monitor->setConnectivity(newConnectivity);
603                               },
604                               Qt::QueuedConnection);
605     return S_OK;
606 }
607 
start()608 bool QNetworkListManagerEvents::start()
609 {
610     if (!connectionPoint) {
611         qCWarning(lcNetMon, "Initialization failed, can't start!");
612         return false;
613     }
614     auto hr = connectionPoint->Advise(this, &cookie);
615     if (FAILED(hr)) {
616         qCWarning(lcNetMon) << "Failed to subscribe to network connectivity events:"
617                             << errorStringFromHResult(hr);
618         return false;
619     }
620 
621     // Update connectivity since it might have changed since this class was constructed
622     NLM_CONNECTIVITY connectivity;
623     hr = networkListManager->GetConnectivity(&connectivity);
624     if (FAILED(hr))
625         qCWarning(lcNetMon) << "Could not get connectivity:" << errorStringFromHResult(hr);
626     else
627         monitor->setConnectivity(connectivity);
628     return true;
629 }
630 
stop()631 bool QNetworkListManagerEvents::stop()
632 {
633     Q_ASSERT(connectionPoint);
634     auto hr = connectionPoint->Unadvise(cookie);
635     if (FAILED(hr)) {
636         qCWarning(lcNetMon) << "Failed to unsubscribe from network connectivity events:"
637                             << errorStringFromHResult(hr);
638         return false;
639     }
640     cookie = 0;
641     return true;
642 }
643 
QNetworkStatusMonitorPrivate()644 QNetworkStatusMonitorPrivate::QNetworkStatusMonitorPrivate()
645 {
646     auto hr = CoInitialize(nullptr);
647     if (FAILED(hr)) {
648         qCWarning(lcNetMon) << "Failed to initialize COM:" << errorStringFromHResult(hr);
649         comInitFailed = true;
650         return;
651     }
652     managerEvents = new QNetworkListManagerEvents(this);
653 }
654 
~QNetworkStatusMonitorPrivate()655 QNetworkStatusMonitorPrivate::~QNetworkStatusMonitorPrivate()
656 {
657     if (comInitFailed)
658         return;
659     if (monitoring)
660         stop();
661 }
662 
setConnectivity(NLM_CONNECTIVITY newConnectivity)663 void QNetworkStatusMonitorPrivate::setConnectivity(NLM_CONNECTIVITY newConnectivity)
664 {
665     Q_Q(QNetworkStatusMonitor);
666 
667     const bool oldAccessibility = q->isNetworkAccessible();
668     connectivity = newConnectivity;
669     const bool accessibility = q->isNetworkAccessible();
670     if (oldAccessibility != accessibility)
671         emit q->onlineStateChanged(accessibility);
672 }
673 
start()674 bool QNetworkStatusMonitorPrivate::start()
675 {
676     Q_ASSERT(!monitoring);
677 
678     if (comInitFailed) {
679         auto hr = CoInitialize(nullptr);
680         if (FAILED(hr)) {
681             qCWarning(lcNetMon) << "Failed to initialize COM:" << errorStringFromHResult(hr);
682             comInitFailed = true;
683             return false;
684         }
685         comInitFailed = false;
686     }
687     if (!managerEvents)
688         managerEvents = new QNetworkListManagerEvents(this);
689 
690     if (managerEvents->start())
691         monitoring = true;
692     return monitoring;
693 }
694 
stop()695 void QNetworkStatusMonitorPrivate::stop()
696 {
697     Q_ASSERT(managerEvents);
698     Q_ASSERT(monitoring);
699     // Can return false but realistically shouldn't since that would break everything:
700     managerEvents->stop();
701     monitoring = false;
702     managerEvents.Reset();
703 
704     CoUninitialize();
705     comInitFailed = true; // we check this value in start() to see if we need to re-initialize
706 }
707 
QNetworkStatusMonitor(QObject * parent)708 QNetworkStatusMonitor::QNetworkStatusMonitor(QObject *parent)
709     : QObject(*new QNetworkStatusMonitorPrivate, parent)
710 {
711 }
712 
~QNetworkStatusMonitor()713 QNetworkStatusMonitor::~QNetworkStatusMonitor() {}
714 
start()715 bool QNetworkStatusMonitor::start()
716 {
717     if (isMonitoring()) {
718         qCWarning(lcNetMon, "Monitor is already active, call stopMonitoring() first");
719         return false;
720     }
721 
722     return d_func()->start();
723 }
724 
stop()725 void QNetworkStatusMonitor::stop()
726 {
727     if (!isMonitoring()) {
728         qCWarning(lcNetMon, "stopMonitoring was called when not monitoring!");
729         return;
730     }
731 
732     d_func()->stop();
733 }
734 
isMonitoring() const735 bool QNetworkStatusMonitor::isMonitoring() const
736 {
737     return d_func()->monitoring;
738 }
739 
isNetworkAccessible()740 bool QNetworkStatusMonitor::isNetworkAccessible()
741 {
742     return d_func()->connectivity
743             & (NLM_CONNECTIVITY_IPV4_INTERNET | NLM_CONNECTIVITY_IPV6_INTERNET
744                | NLM_CONNECTIVITY_IPV4_SUBNET | NLM_CONNECTIVITY_IPV6_SUBNET
745                | NLM_CONNECTIVITY_IPV4_LOCALNETWORK | NLM_CONNECTIVITY_IPV6_LOCALNETWORK);
746 }
747 
event(QEvent * event)748 bool QNetworkStatusMonitor::event(QEvent *event)
749 {
750     if (event->type() == QEvent::ThreadChange && isMonitoring()) {
751         stop();
752         QMetaObject::invokeMethod(this, &QNetworkStatusMonitor::start, Qt::QueuedConnection);
753     }
754 
755     return QObject::event(event);
756 }
757 
isEnabled()758 bool QNetworkStatusMonitor::isEnabled()
759 {
760     return true;
761 }
762 
reachabilityChanged(bool online)763 void QNetworkStatusMonitor::reachabilityChanged(bool online)
764 {
765     Q_UNUSED(online);
766     Q_UNREACHABLE();
767 }
768 
769 QT_END_NAMESPACE
770