Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qnetconmonitor_win.cpp
Go to the documentation of this file.
1// Copyright (C) 2019 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// Qt-Security score:significant reason:default
4
6
7#include "private/qobject_p.h"
8
9#include <QtCore/quuid.h>
10#include <QtCore/qmetaobject.h>
11
12#include <QtCore/private/qfunctions_win_p.h>
13#include <QtCore/private/qsystemerror_p.h>
14
15#include <QtNetwork/qnetworkinterface.h>
16
17#include <objbase.h>
18#include <netlistmgr.h>
19#include <QtCore/private/qcomptr_p.h>
20#include <wrl/wrappers/corewrappers.h>
21#include <iphlpapi.h>
22
23#include <algorithm>
24
25using namespace Microsoft::WRL;
26
28
29Q_LOGGING_CATEGORY(lcNetMon, "qt.network.monitor");
30
31namespace {
32template<typename T>
33bool QueryInterfaceImpl(IUnknown *from, REFIID riid, void **ppvObject)
34{
35 if (riid == __uuidof(T)) {
36 *ppvObject = static_cast<T *>(from);
37 from->AddRef();
38 return true;
39 }
40 return false;
41}
42
43QNetworkInterface getInterfaceFromHostAddress(const QHostAddress &local)
44{
45 QList<QNetworkInterface> interfaces = QNetworkInterface::allInterfaces();
46 auto it = std::find_if(
47 interfaces.cbegin(), interfaces.cend(), [&local](const QNetworkInterface &iface) {
48 const auto &entries = iface.addressEntries();
49 return std::any_of(entries.cbegin(), entries.cend(),
50 [&local](const QNetworkAddressEntry &entry) {
51 return entry.ip().isEqual(local,
52 QHostAddress::TolerantConversion);
53 });
54 });
55 if (it == interfaces.cend()) {
56 qCDebug(lcNetMon, "Could not find the interface for the local address.");
57 return {};
58 }
59 return *it;
60}
61} // anonymous namespace
62
64{
65public:
68
69 HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObject) override;
70
73 {
74 if (--ref == 0) {
75 delete this;
76 return 0;
77 }
78 return ref;
79 }
80
85
86 [[nodiscard]]
87 bool setTarget(const QNetworkInterface &iface);
88 [[nodiscard]]
90 [[nodiscard]]
92
93private:
94 ComPtr<INetworkConnection> getNetworkConnectionFromAdapterGuid(QUuid guid);
95
96 QUuid currentConnectionId{};
97
98 ComPtr<INetworkListManager> networkListManager;
99 ComPtr<IConnectionPoint> connectionPoint;
100
101 QNetworkConnectionMonitorPrivate *monitor = nullptr;
102
103 QAtomicInteger<ULONG> ref = 0;
104 DWORD cookie = 0;
105};
106
108{
109 Q_DECLARE_PUBLIC(QNetworkConnectionMonitor);
110
111public:
114
115 [[nodiscard]]
116 bool setTargets(const QHostAddress &local, const QHostAddress &remote);
117 [[nodiscard]]
120
121 void setConnectivity(NLM_CONNECTIVITY newConnectivity);
122
123private:
124 QComHelper comHelper;
125
126 ComPtr<QNetworkConnectionEvents> connectionEvents;
127 // We can assume we have access to internet/subnet when this class is created because
128 // connection has already been established to the peer:
129 NLM_CONNECTIVITY connectivity = NLM_CONNECTIVITY(
134
135 bool sameSubnet = false;
136 bool isLinkLocal = false;
137 bool monitoring = false;
138 bool remoteIsIPv6 = false;
139};
140
142 : monitor(monitor)
143{
144 auto hr = CoCreateInstance(CLSID_NetworkListManager, nullptr, CLSCTX_INPROC_SERVER,
145 IID_INetworkListManager, &networkListManager);
146 if (FAILED(hr)) {
147 qCDebug(lcNetMon) << "Could not get a NetworkListManager instance:"
148 << QSystemError::windowsComString(hr);
149 return;
150 }
151
152 ComPtr<IConnectionPointContainer> connectionPointContainer;
153 hr = networkListManager.As(&connectionPointContainer);
154 if (SUCCEEDED(hr)) {
155 hr = connectionPointContainer->FindConnectionPoint(IID_INetworkConnectionEvents,
156 &connectionPoint);
157 }
158 if (FAILED(hr)) {
159 qCDebug(lcNetMon) << "Failed to get connection point for network events:"
160 << QSystemError::windowsComString(hr);
161 }
162}
163
165{
166 Q_ASSERT(ref == 0);
167}
168
169ComPtr<INetworkConnection> QNetworkConnectionEvents::getNetworkConnectionFromAdapterGuid(QUuid guid)
170{
171 if (!networkListManager) {
172 qCDebug(lcNetMon) << "Failed to enumerate network connections:"
173 << "NetworkListManager was not instantiated";
174 return nullptr;
175 }
176
177 ComPtr<IEnumNetworkConnections> connections;
178 auto hr = networkListManager->GetNetworkConnections(connections.GetAddressOf());
179 if (FAILED(hr)) {
180 qCDebug(lcNetMon) << "Failed to enumerate network connections:"
181 << QSystemError::windowsComString(hr);
182 return nullptr;
183 }
184 ComPtr<INetworkConnection> connection = nullptr;
185 do {
186 hr = connections->Next(1, connection.GetAddressOf(), nullptr);
187 if (FAILED(hr)) {
188 qCDebug(lcNetMon) << "Failed to get next network connection in enumeration:"
189 << QSystemError::windowsComString(hr);
190 break;
191 }
192 if (connection) {
193 GUID adapterId;
194 hr = connection->GetAdapterId(&adapterId);
195 if (FAILED(hr)) {
196 qCDebug(lcNetMon) << "Failed to get adapter ID from network connection:"
197 << QSystemError::windowsComString(hr);
198 continue;
199 }
200 if (guid == adapterId)
201 return connection;
202 }
203 } while (connection);
204 return nullptr;
205}
206
207HRESULT STDMETHODCALLTYPE QNetworkConnectionEvents::QueryInterface(REFIID riid, void **ppvObject)
208{
209 if (!ppvObject)
210 return E_INVALIDARG;
211
212 return QueryInterfaceImpl<IUnknown>(this, riid, ppvObject)
213 || QueryInterfaceImpl<INetworkConnectionEvents>(this, riid, ppvObject)
214 ? S_OK
215 : E_NOINTERFACE;
216}
217
218HRESULT STDMETHODCALLTYPE QNetworkConnectionEvents::NetworkConnectionConnectivityChanged(
219 GUID connectionId, NLM_CONNECTIVITY newConnectivity)
220{
221 // This function is run on a different thread than 'monitor' is created on, so we need to run
222 // it on that thread
223 QMetaObject::invokeMethod(monitor->q_ptr,
224 [this, connectionId, newConnectivity, monitor = this->monitor]() {
225 if (connectionId == currentConnectionId)
226 monitor->setConnectivity(newConnectivity);
227 },
228 Qt::QueuedConnection);
229 return S_OK;
230}
231
232HRESULT STDMETHODCALLTYPE QNetworkConnectionEvents::NetworkConnectionPropertyChanged(
233 GUID connectionId, NLM_CONNECTION_PROPERTY_CHANGE flags)
234{
235 Q_UNUSED(connectionId);
236 Q_UNUSED(flags);
237 return E_NOTIMPL;
238}
239
240bool QNetworkConnectionEvents::setTarget(const QNetworkInterface &iface)
241{
242 // Unset this in case it's already set to something
243 currentConnectionId = QUuid{};
244
245 NET_LUID luid;
246 if (ConvertInterfaceIndexToLuid(iface.index(), &luid) != NO_ERROR) {
247 qCDebug(lcNetMon, "Could not get the LUID for the interface.");
248 return false;
249 }
250 GUID guid;
251 if (ConvertInterfaceLuidToGuid(&luid, &guid) != NO_ERROR) {
252 qCDebug(lcNetMon, "Could not get the GUID for the interface.");
253 return false;
254 }
255 ComPtr<INetworkConnection> connection = getNetworkConnectionFromAdapterGuid(guid);
256 if (!connection) {
257 qCDebug(lcNetMon, "Could not get the INetworkConnection instance for the adapter GUID.");
258 return false;
259 }
260 auto hr = connection->GetConnectionId(&guid);
261 if (FAILED(hr)) {
262 qCDebug(lcNetMon) << "Failed to get the connection's GUID:"
263 << QSystemError::windowsComString(hr);
264 return false;
265 }
266 currentConnectionId = guid;
267
268 return true;
269}
270
272{
273 if (currentConnectionId.isNull()) {
274 qCDebug(lcNetMon, "Can not start monitoring, set targets first");
275 return false;
276 }
277 if (!connectionPoint) {
278 qCDebug(lcNetMon,
279 "We don't have the connection point, cannot start listening to events!");
280 return false;
281 }
282
283 auto hr = connectionPoint->Advise(this, &cookie);
284 if (FAILED(hr)) {
285 qCDebug(lcNetMon) << "Failed to subscribe to network connectivity events:"
286 << QSystemError::windowsComString(hr);
287 return false;
288 }
289 return true;
290}
291
293{
294 auto hr = connectionPoint->Unadvise(cookie);
295 if (FAILED(hr)) {
296 qCDebug(lcNetMon) << "Failed to unsubscribe from network connection events:"
297 << QSystemError::windowsComString(hr);
298 return false;
299 }
300 cookie = 0;
301 currentConnectionId = QUuid{};
302 return true;
303}
304
306{
307 if (!comHelper.isValid())
308 return;
309
310 connectionEvents = new QNetworkConnectionEvents(this);
311}
312
314{
315 if (!comHelper.isValid())
316 return;
317 if (monitoring)
319 connectionEvents.Reset();
320}
321
322bool QNetworkConnectionMonitorPrivate::setTargets(const QHostAddress &local,
323 const QHostAddress &remote)
324{
325 if (!comHelper.isValid())
326 return false;
327
328 QNetworkInterface iface = getInterfaceFromHostAddress(local);
329 if (!iface.isValid())
330 return false;
331 const auto &addressEntries = iface.addressEntries();
332 auto it = std::find_if(
333 addressEntries.cbegin(), addressEntries.cend(),
334 [&local](const QNetworkAddressEntry &entry) { return entry.ip() == local; });
335 if (Q_UNLIKELY(it == addressEntries.cend())) {
336 qCDebug(lcNetMon, "The address entry we were working with disappeared");
337 return false;
338 }
339 sameSubnet = remote.isInSubnet(local, it->prefixLength());
340 isLinkLocal = remote.isLinkLocal() && local.isLinkLocal();
341 remoteIsIPv6 = remote.protocol() == QAbstractSocket::IPv6Protocol;
342
343 return connectionEvents->setTarget(iface);
344}
345
346void QNetworkConnectionMonitorPrivate::setConnectivity(NLM_CONNECTIVITY newConnectivity)
347{
348 Q_Q(QNetworkConnectionMonitor);
349 const bool reachable = q->isReachable();
350 connectivity = newConnectivity;
351 const bool newReachable = q->isReachable();
352 if (reachable != newReachable)
353 emit q->reachabilityChanged(newReachable);
354}
355
357{
358 Q_ASSERT(connectionEvents);
359 Q_ASSERT(!monitoring);
360 if (connectionEvents->startMonitoring())
361 monitoring = true;
362 return monitoring;
363}
364
366{
367 Q_ASSERT(connectionEvents);
368 Q_ASSERT(monitoring);
369 if (connectionEvents->stopMonitoring())
370 monitoring = false;
371}
372
373QNetworkConnectionMonitor::QNetworkConnectionMonitor()
374 : QObject(*new QNetworkConnectionMonitorPrivate)
375{
376}
377
378QNetworkConnectionMonitor::QNetworkConnectionMonitor(const QHostAddress &local,
379 const QHostAddress &remote)
380 : QObject(*new QNetworkConnectionMonitorPrivate)
381{
382 setTargets(local, remote);
383}
384
385QNetworkConnectionMonitor::~QNetworkConnectionMonitor() = default;
386
387bool QNetworkConnectionMonitor::setTargets(const QHostAddress &local, const QHostAddress &remote)
388{
389 if (isMonitoring()) {
390 qCDebug(lcNetMon, "Monitor is already active, call stopMonitoring() first");
391 return false;
392 }
393 if (local.isNull()) {
394 qCDebug(lcNetMon, "Invalid (null) local address, cannot create a reachability target");
395 return false;
396 }
397 // Silently return false for loopback addresses instead of printing warnings later
398 if (remote.isLoopback())
399 return false;
400
401 return d_func()->setTargets(local, remote);
402}
403
404bool QNetworkConnectionMonitor::startMonitoring()
405{
406 Q_D(QNetworkConnectionMonitor);
407 if (isMonitoring()) {
408 qCDebug(lcNetMon, "Monitor is already active, call stopMonitoring() first");
409 return false;
410 }
411 return d->startMonitoring();
412}
413
414bool QNetworkConnectionMonitor::isMonitoring() const
415{
416 return d_func()->monitoring;
417}
418
419void QNetworkConnectionMonitor::stopMonitoring()
420{
421 Q_D(QNetworkConnectionMonitor);
422 if (!isMonitoring()) {
423 qCDebug(lcNetMon, "stopMonitoring was called when not monitoring!");
424 return;
425 }
426 d->stopMonitoring();
427}
428
429bool QNetworkConnectionMonitor::isReachable()
430{
431 Q_D(QNetworkConnectionMonitor);
432
433 const NLM_CONNECTIVITY RequiredSameSubnetIPv6 =
434 NLM_CONNECTIVITY(NLM_CONNECTIVITY_IPV6_SUBNET | NLM_CONNECTIVITY_IPV6_LOCALNETWORK
435 | NLM_CONNECTIVITY_IPV6_INTERNET);
436 const NLM_CONNECTIVITY RequiredSameSubnetIPv4 =
437 NLM_CONNECTIVITY(NLM_CONNECTIVITY_IPV4_SUBNET | NLM_CONNECTIVITY_IPV4_LOCALNETWORK
438 | NLM_CONNECTIVITY_IPV4_INTERNET);
439
440 NLM_CONNECTIVITY required;
441 if (d->isLinkLocal) {
442 required = NLM_CONNECTIVITY(
443 d->remoteIsIPv6 ? NLM_CONNECTIVITY_IPV6_NOTRAFFIC | RequiredSameSubnetIPv6
444 : NLM_CONNECTIVITY_IPV4_NOTRAFFIC | RequiredSameSubnetIPv4);
445 } else if (d->sameSubnet) {
446 required =
447 NLM_CONNECTIVITY(d->remoteIsIPv6 ? RequiredSameSubnetIPv6 : RequiredSameSubnetIPv4);
448
449 } else {
450 required = NLM_CONNECTIVITY(d->remoteIsIPv6 ? NLM_CONNECTIVITY_IPV6_INTERNET
451 : NLM_CONNECTIVITY_IPV4_INTERNET);
452 }
453
454 return d_func()->connectivity & required;
455}
456
457bool QNetworkConnectionMonitor::isEnabled()
458{
459 return true;
460}
461
462QT_END_NAMESPACE
bool setTarget(const QNetworkInterface &iface)
QNetworkConnectionEvents(QNetworkConnectionMonitorPrivate *monitor)
bool setTargets(const QHostAddress &local, const QHostAddress &remote)
void setConnectivity(NLM_CONNECTIVITY newConnectivity)
Q_LOGGING_CATEGORY(lcEventDispatcher, "qt.eventdispatcher")