Qt 6.x
The Qt SDK
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
4#include "qnetconmonitor_p.h"
5
6#include "private/qobject_p.h"
7
8#include <QtCore/quuid.h>
9#include <QtCore/qmetaobject.h>
10
11#include <QtCore/private/qfunctions_win_p.h>
12#include <QtCore/private/qsystemerror_p.h>
13
14#include <QtNetwork/qnetworkinterface.h>
15
16#include <objbase.h>
17#include <netlistmgr.h>
18#include <wrl/client.h>
19#include <wrl/wrappers/corewrappers.h>
20#include <iphlpapi.h>
21
22#include <algorithm>
23
24using namespace Microsoft::WRL;
25
27
28Q_LOGGING_CATEGORY(lcNetMon, "qt.network.monitor");
29
30namespace {
31template<typename T>
32bool QueryInterfaceImpl(IUnknown *from, REFIID riid, void **ppvObject)
33{
34 if (riid == __uuidof(T)) {
35 *ppvObject = static_cast<T *>(from);
36 from->AddRef();
37 return true;
38 }
39 return false;
40}
41
42QNetworkInterface getInterfaceFromHostAddress(const QHostAddress &local)
43{
45 auto it = std::find_if(
46 interfaces.cbegin(), interfaces.cend(), [&local](const QNetworkInterface &iface) {
47 const auto &entries = iface.addressEntries();
48 return std::any_of(entries.cbegin(), entries.cend(),
49 [&local](const QNetworkAddressEntry &entry) {
50 return entry.ip().isEqual(local,
51 QHostAddress::TolerantConversion);
52 });
53 });
54 if (it == interfaces.cend()) {
55 qCDebug(lcNetMon, "Could not find the interface for the local address.");
56 return {};
57 }
58 return *it;
59}
60} // anonymous namespace
61
62class QNetworkConnectionEvents : public INetworkConnectionEvents
63{
64public:
67
68 HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObject) override;
69
70 ULONG STDMETHODCALLTYPE AddRef() override { return ++ref; }
71 ULONG STDMETHODCALLTYPE Release() override
72 {
73 if (--ref == 0) {
74 delete this;
75 return 0;
76 }
77 return ref;
78 }
79
80 HRESULT STDMETHODCALLTYPE
81 NetworkConnectionConnectivityChanged(GUID connectionId, NLM_CONNECTIVITY connectivity) override;
83 GUID connectionId, NLM_CONNECTION_PROPERTY_CHANGE flags) override;
84
85 [[nodiscard]]
86 bool setTarget(const QNetworkInterface &iface);
87 [[nodiscard]]
88 bool startMonitoring();
89 [[nodiscard]]
90 bool stopMonitoring();
91
92private:
93 ComPtr<INetworkConnection> getNetworkConnectionFromAdapterGuid(QUuid guid);
94
95 QUuid currentConnectionId{};
96
97 ComPtr<INetworkListManager> networkListManager;
98 ComPtr<IConnectionPoint> connectionPoint;
99
100 QNetworkConnectionMonitorPrivate *monitor = nullptr;
101
103 DWORD cookie = 0;
104};
105
107{
108 Q_DECLARE_PUBLIC(QNetworkConnectionMonitor);
109
110public:
113
114 [[nodiscard]]
115 bool setTargets(const QHostAddress &local, const QHostAddress &remote);
116 [[nodiscard]]
117 bool startMonitoring();
118 void stopMonitoring();
119
120 void setConnectivity(NLM_CONNECTIVITY newConnectivity);
121
122private:
123 QComHelper comHelper;
124
125 ComPtr<QNetworkConnectionEvents> connectionEvents;
126 // We can assume we have access to internet/subnet when this class is created because
127 // connection has already been established to the peer:
128 NLM_CONNECTIVITY connectivity = NLM_CONNECTIVITY(
129 NLM_CONNECTIVITY_IPV4_INTERNET | NLM_CONNECTIVITY_IPV6_INTERNET
130 | NLM_CONNECTIVITY_IPV4_SUBNET | NLM_CONNECTIVITY_IPV6_SUBNET
131 | NLM_CONNECTIVITY_IPV4_LOCALNETWORK | NLM_CONNECTIVITY_IPV6_LOCALNETWORK
132 | NLM_CONNECTIVITY_IPV4_NOTRAFFIC | NLM_CONNECTIVITY_IPV6_NOTRAFFIC);
133
134 bool sameSubnet = false;
135 bool isLinkLocal = false;
136 bool monitoring = false;
137 bool remoteIsIPv6 = false;
138};
139
141 : monitor(monitor)
142{
143 auto hr = CoCreateInstance(CLSID_NetworkListManager, nullptr, CLSCTX_INPROC_SERVER,
144 IID_INetworkListManager, &networkListManager);
145 if (FAILED(hr)) {
146 qCDebug(lcNetMon) << "Could not get a NetworkListManager instance:"
147 << QSystemError::windowsComString(hr);
148 return;
149 }
150
151 ComPtr<IConnectionPointContainer> connectionPointContainer;
152 hr = networkListManager.As(&connectionPointContainer);
153 if (SUCCEEDED(hr)) {
154 hr = connectionPointContainer->FindConnectionPoint(IID_INetworkConnectionEvents,
155 &connectionPoint);
156 }
157 if (FAILED(hr)) {
158 qCDebug(lcNetMon) << "Failed to get connection point for network events:"
159 << QSystemError::windowsComString(hr);
160 }
161}
162
164{
165 Q_ASSERT(ref == 0);
166}
167
168ComPtr<INetworkConnection> QNetworkConnectionEvents::getNetworkConnectionFromAdapterGuid(QUuid guid)
169{
171 auto hr = networkListManager->GetNetworkConnections(connections.GetAddressOf());
172 if (FAILED(hr)) {
173 qCDebug(lcNetMon) << "Failed to enumerate network connections:"
174 << QSystemError::windowsComString(hr);
175 return nullptr;
176 }
178 do {
179 hr = connections->Next(1, connection.GetAddressOf(), nullptr);
180 if (FAILED(hr)) {
181 qCDebug(lcNetMon) << "Failed to get next network connection in enumeration:"
182 << QSystemError::windowsComString(hr);
183 break;
184 }
185 if (connection) {
186 GUID adapterId;
187 hr = connection->GetAdapterId(&adapterId);
188 if (FAILED(hr)) {
189 qCDebug(lcNetMon) << "Failed to get adapter ID from network connection:"
190 << QSystemError::windowsComString(hr);
191 continue;
192 }
193 if (guid == adapterId)
194 return connection;
195 }
196 } while (connection);
197 return nullptr;
198}
199
200HRESULT STDMETHODCALLTYPE QNetworkConnectionEvents::QueryInterface(REFIID riid, void **ppvObject)
201{
202 if (!ppvObject)
203 return E_INVALIDARG;
204
205 return QueryInterfaceImpl<IUnknown>(this, riid, ppvObject)
206 || QueryInterfaceImpl<INetworkConnectionEvents>(this, riid, ppvObject)
207 ? S_OK
208 : E_NOINTERFACE;
209}
210
212 GUID connectionId, NLM_CONNECTIVITY newConnectivity)
213{
214 // This function is run on a different thread than 'monitor' is created on, so we need to run
215 // it on that thread
217 [this, connectionId, newConnectivity, monitor = this->monitor]() {
218 if (connectionId == currentConnectionId)
219 monitor->setConnectivity(newConnectivity);
220 },
222 return S_OK;
223}
224
226 GUID connectionId, NLM_CONNECTION_PROPERTY_CHANGE flags)
227{
228 Q_UNUSED(connectionId);
230 return E_NOTIMPL;
231}
232
234{
235 // Unset this in case it's already set to something
236 currentConnectionId = QUuid{};
237
238 NET_LUID luid;
239 if (ConvertInterfaceIndexToLuid(iface.index(), &luid) != NO_ERROR) {
240 qCDebug(lcNetMon, "Could not get the LUID for the interface.");
241 return false;
242 }
243 GUID guid;
244 if (ConvertInterfaceLuidToGuid(&luid, &guid) != NO_ERROR) {
245 qCDebug(lcNetMon, "Could not get the GUID for the interface.");
246 return false;
247 }
248 ComPtr<INetworkConnection> connection = getNetworkConnectionFromAdapterGuid(guid);
249 if (!connection) {
250 qCDebug(lcNetMon, "Could not get the INetworkConnection instance for the adapter GUID.");
251 return false;
252 }
253 auto hr = connection->GetConnectionId(&guid);
254 if (FAILED(hr)) {
255 qCDebug(lcNetMon) << "Failed to get the connection's GUID:"
256 << QSystemError::windowsComString(hr);
257 return false;
258 }
259 currentConnectionId = guid;
260
261 return true;
262}
263
265{
266 if (currentConnectionId.isNull()) {
267 qCDebug(lcNetMon, "Can not start monitoring, set targets first");
268 return false;
269 }
270 if (!connectionPoint) {
271 qCDebug(lcNetMon,
272 "We don't have the connection point, cannot start listening to events!");
273 return false;
274 }
275
276 auto hr = connectionPoint->Advise(this, &cookie);
277 if (FAILED(hr)) {
278 qCDebug(lcNetMon) << "Failed to subscribe to network connectivity events:"
279 << QSystemError::windowsComString(hr);
280 return false;
281 }
282 return true;
283}
284
286{
287 auto hr = connectionPoint->Unadvise(cookie);
288 if (FAILED(hr)) {
289 qCDebug(lcNetMon) << "Failed to unsubscribe from network connection events:"
290 << QSystemError::windowsComString(hr);
291 return false;
292 }
293 cookie = 0;
294 currentConnectionId = QUuid{};
295 return true;
296}
297
299{
300 if (!comHelper.isValid())
301 return;
302
303 connectionEvents = new QNetworkConnectionEvents(this);
304}
305
307{
308 if (!comHelper.isValid())
309 return;
310 if (monitoring)
312 connectionEvents.Reset();
313}
314
316 const QHostAddress &remote)
317{
318 if (!comHelper.isValid())
319 return false;
320
321 QNetworkInterface iface = getInterfaceFromHostAddress(local);
322 if (!iface.isValid())
323 return false;
324 const auto &addressEntries = iface.addressEntries();
325 auto it = std::find_if(
326 addressEntries.cbegin(), addressEntries.cend(),
327 [&local](const QNetworkAddressEntry &entry) { return entry.ip() == local; });
328 if (Q_UNLIKELY(it == addressEntries.cend())) {
329 qCDebug(lcNetMon, "The address entry we were working with disappeared");
330 return false;
331 }
332 sameSubnet = remote.isInSubnet(local, it->prefixLength());
333 isLinkLocal = remote.isLinkLocal() && local.isLinkLocal();
334 remoteIsIPv6 = remote.protocol() == QAbstractSocket::IPv6Protocol;
335
336 return connectionEvents->setTarget(iface);
337}
338
339void QNetworkConnectionMonitorPrivate::setConnectivity(NLM_CONNECTIVITY newConnectivity)
340{
342 const bool reachable = q->isReachable();
343 connectivity = newConnectivity;
344 const bool newReachable = q->isReachable();
345 if (reachable != newReachable)
346 emit q->reachabilityChanged(newReachable);
347}
348
350{
351 Q_ASSERT(connectionEvents);
352 Q_ASSERT(!monitoring);
353 if (connectionEvents->startMonitoring())
354 monitoring = true;
355 return monitoring;
356}
357
359{
360 Q_ASSERT(connectionEvents);
361 Q_ASSERT(monitoring);
362 if (connectionEvents->stopMonitoring())
363 monitoring = false;
364}
365
368{
369}
370
372 const QHostAddress &remote)
374{
375 setTargets(local, remote);
376}
377
379
381{
382 if (isMonitoring()) {
383 qCDebug(lcNetMon, "Monitor is already active, call stopMonitoring() first");
384 return false;
385 }
386 if (local.isNull()) {
387 qCDebug(lcNetMon, "Invalid (null) local address, cannot create a reachability target");
388 return false;
389 }
390 // Silently return false for loopback addresses instead of printing warnings later
391 if (remote.isLoopback())
392 return false;
393
394 return d_func()->setTargets(local, remote);
395}
396
398{
400 if (isMonitoring()) {
401 qCDebug(lcNetMon, "Monitor is already active, call stopMonitoring() first");
402 return false;
403 }
404 return d->startMonitoring();
405}
406
408{
409 return d_func()->monitoring;
410}
411
413{
415 if (!isMonitoring()) {
416 qCDebug(lcNetMon, "stopMonitoring was called when not monitoring!");
417 return;
418 }
419 d->stopMonitoring();
420}
421
423{
425
426 const NLM_CONNECTIVITY RequiredSameSubnetIPv6 =
427 NLM_CONNECTIVITY(NLM_CONNECTIVITY_IPV6_SUBNET | NLM_CONNECTIVITY_IPV6_LOCALNETWORK
428 | NLM_CONNECTIVITY_IPV6_INTERNET);
429 const NLM_CONNECTIVITY RequiredSameSubnetIPv4 =
430 NLM_CONNECTIVITY(NLM_CONNECTIVITY_IPV4_SUBNET | NLM_CONNECTIVITY_IPV4_LOCALNETWORK
431 | NLM_CONNECTIVITY_IPV4_INTERNET);
432
433 NLM_CONNECTIVITY required;
434 if (d->isLinkLocal) {
435 required = NLM_CONNECTIVITY(
436 d->remoteIsIPv6 ? NLM_CONNECTIVITY_IPV6_NOTRAFFIC | RequiredSameSubnetIPv6
437 : NLM_CONNECTIVITY_IPV4_NOTRAFFIC | RequiredSameSubnetIPv4);
438 } else if (d->sameSubnet) {
439 required =
440 NLM_CONNECTIVITY(d->remoteIsIPv6 ? RequiredSameSubnetIPv6 : RequiredSameSubnetIPv4);
441
442 } else {
443 required = NLM_CONNECTIVITY(d->remoteIsIPv6 ? NLM_CONNECTIVITY_IPV6_INTERNET
444 : NLM_CONNECTIVITY_IPV4_INTERNET);
445 }
446
447 return d_func()->connectivity & required;
448}
449
451{
452 return true;
453}
454
static constexpr auto IPv6Protocol
\inmodule QtCore
Definition qatomic.h:18
The QHostAddress class provides an IP address.
bool isLinkLocal() const
bool isLoopback() const
bool isNull() const
Returns true if this host address is not valid for any host or interface.
bool isInSubnet(const QHostAddress &subnet, int netmask) const
NetworkLayerProtocol protocol() const
Returns the network layer protocol of the host address.
Definition qlist.h:74
const_iterator cend() const noexcept
Definition qlist.h:614
const_iterator cbegin() const noexcept
Definition qlist.h:613
The QNetworkAddressEntry class stores one IP address supported by a network interface,...
HRESULT STDMETHODCALLTYPE NetworkConnectionPropertyChanged(GUID connectionId, NLM_CONNECTION_PROPERTY_CHANGE flags) override
HRESULT STDMETHODCALLTYPE NetworkConnectionConnectivityChanged(GUID connectionId, NLM_CONNECTIVITY connectivity) override
ULONG STDMETHODCALLTYPE Release() override
bool setTarget(const QNetworkInterface &iface)
QNetworkConnectionEvents(QNetworkConnectionMonitorPrivate *monitor)
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObject) override
ULONG STDMETHODCALLTYPE AddRef() override
bool setTargets(const QHostAddress &local, const QHostAddress &remote)
void setConnectivity(NLM_CONNECTIVITY newConnectivity)
bool setTargets(const QHostAddress &local, const QHostAddress &remote)
The QNetworkInterface class provides a listing of the host's IP addresses and network interfaces.
static QList< QNetworkInterface > allInterfaces()
Returns a listing of all the network interfaces found on the host machine.
QObject * q_ptr
Definition qobject.h:60
\inmodule QtCore
Definition qobject.h:90
const_iterator cend() const noexcept
Definition qset.h:142
\inmodule QtCore
Definition quuid.h:31
bool isNull() const noexcept
Returns true if this is the null UUID {00000000-0000-0000-0000-000000000000}; otherwise returns false...
Definition quuid.cpp:768
#define this
Definition dialogs.cpp:9
QSet< QString >::iterator it
Combined button and popup list for selecting options.
@ QueuedConnection
#define Q_UNLIKELY(x)
DBusConnection * connection
#define Q_LOGGING_CATEGORY(name,...)
#define qCDebug(category,...)
GLbitfield flags
GLint ref
GLuint entry
GLdouble GLdouble GLdouble GLdouble q
Definition qopenglext.h:259
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
#define emit
#define Q_UNUSED(x)
IUIViewSettingsInterop __RPC__in REFIID riid
long HRESULT
static bool invokeMethod(QObject *obj, const char *member, Qt::ConnectionType, QGenericReturnArgument ret, QGenericArgument val0=QGenericArgument(nullptr), QGenericArgument val1=QGenericArgument(), QGenericArgument val2=QGenericArgument(), QGenericArgument val3=QGenericArgument(), QGenericArgument val4=QGenericArgument(), QGenericArgument val5=QGenericArgument(), QGenericArgument val6=QGenericArgument(), QGenericArgument val7=QGenericArgument(), QGenericArgument val8=QGenericArgument(), QGenericArgument val9=QGenericArgument())
\threadsafe This is an overloaded member function, provided for convenience. It differs from the abov...