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