Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
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{
44 QList<QNetworkInterface> interfaces = QNetworkInterface::allInterfaces();
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
102 QAtomicInteger<ULONG> ref = 0;
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
167
168ComPtr<INetworkConnection> QNetworkConnectionEvents::getNetworkConnectionFromAdapterGuid(QUuid guid)
169{
170 if (!networkListManager) {
171 qCDebug(lcNetMon) << "Failed to enumerate network connections:"
172 << "NetworkListManager was not instantiated";
173 return nullptr;
174 }
175
176 ComPtr<IEnumNetworkConnections> connections;
177 auto hr = networkListManager->GetNetworkConnections(connections.GetAddressOf());
178 if (FAILED(hr)) {
179 qCDebug(lcNetMon) << "Failed to enumerate network connections:"
180 << QSystemError::windowsComString(hr);
181 return nullptr;
182 }
183 ComPtr<INetworkConnection> connection = nullptr;
184 do {
185 hr = connections->Next(1, connection.GetAddressOf(), nullptr);
186 if (FAILED(hr)) {
187 qCDebug(lcNetMon) << "Failed to get next network connection in enumeration:"
188 << QSystemError::windowsComString(hr);
189 break;
190 }
191 if (connection) {
192 GUID adapterId;
193 hr = connection->GetAdapterId(&adapterId);
194 if (FAILED(hr)) {
195 qCDebug(lcNetMon) << "Failed to get adapter ID from network connection:"
196 << QSystemError::windowsComString(hr);
197 continue;
198 }
199 if (guid == adapterId)
200 return connection;
201 }
202 } while (connection);
203 return nullptr;
204}
205
206HRESULT STDMETHODCALLTYPE QNetworkConnectionEvents::QueryInterface(REFIID riid, void **ppvObject)
207{
208 if (!ppvObject)
209 return E_INVALIDARG;
210
211 return QueryInterfaceImpl<IUnknown>(this, riid, ppvObject)
212 || QueryInterfaceImpl<INetworkConnectionEvents>(this, riid, ppvObject)
213 ? S_OK
214 : E_NOINTERFACE;
215}
216
218 GUID connectionId, NLM_CONNECTIVITY newConnectivity)
219{
220 // This function is run on a different thread than 'monitor' is created on, so we need to run
221 // it on that thread
223 [this, connectionId, newConnectivity, monitor = this->monitor]() {
224 if (connectionId == currentConnectionId)
225 monitor->setConnectivity(newConnectivity);
226 },
228 return S_OK;
229}
230
232 GUID connectionId, NLM_CONNECTION_PROPERTY_CHANGE flags)
233{
234 Q_UNUSED(connectionId);
236 return E_NOTIMPL;
237}
238
240{
241 // Unset this in case it's already set to something
242 currentConnectionId = QUuid{};
243
244 NET_LUID luid;
245 if (ConvertInterfaceIndexToLuid(iface.index(), &luid) != NO_ERROR) {
246 qCDebug(lcNetMon, "Could not get the LUID for the interface.");
247 return false;
248 }
249 GUID guid;
250 if (ConvertInterfaceLuidToGuid(&luid, &guid) != NO_ERROR) {
251 qCDebug(lcNetMon, "Could not get the GUID for the interface.");
252 return false;
253 }
254 ComPtr<INetworkConnection> connection = getNetworkConnectionFromAdapterGuid(guid);
255 if (!connection) {
256 qCDebug(lcNetMon, "Could not get the INetworkConnection instance for the adapter GUID.");
257 return false;
258 }
259 auto hr = connection->GetConnectionId(&guid);
260 if (FAILED(hr)) {
261 qCDebug(lcNetMon) << "Failed to get the connection's GUID:"
262 << QSystemError::windowsComString(hr);
263 return false;
264 }
265 currentConnectionId = guid;
266
267 return true;
268}
269
271{
272 if (currentConnectionId.isNull()) {
273 qCDebug(lcNetMon, "Can not start monitoring, set targets first");
274 return false;
275 }
276 if (!connectionPoint) {
277 qCDebug(lcNetMon,
278 "We don't have the connection point, cannot start listening to events!");
279 return false;
280 }
281
282 auto hr = connectionPoint->Advise(this, &cookie);
283 if (FAILED(hr)) {
284 qCDebug(lcNetMon) << "Failed to subscribe to network connectivity events:"
285 << QSystemError::windowsComString(hr);
286 return false;
287 }
288 return true;
289}
290
292{
293 auto hr = connectionPoint->Unadvise(cookie);
294 if (FAILED(hr)) {
295 qCDebug(lcNetMon) << "Failed to unsubscribe from network connection events:"
296 << QSystemError::windowsComString(hr);
297 return false;
298 }
299 cookie = 0;
300 currentConnectionId = QUuid{};
301 return true;
302}
303
305{
306 if (!comHelper.isValid())
307 return;
308
309 connectionEvents = new QNetworkConnectionEvents(this);
310}
311
313{
314 if (!comHelper.isValid())
315 return;
316 if (monitoring)
318 connectionEvents.Reset();
319}
320
322 const QHostAddress &remote)
323{
324 if (!comHelper.isValid())
325 return false;
326
327 QNetworkInterface iface = getInterfaceFromHostAddress(local);
328 if (!iface.isValid())
329 return false;
330 const auto &addressEntries = iface.addressEntries();
331 auto it = std::find_if(
332 addressEntries.cbegin(), addressEntries.cend(),
333 [&local](const QNetworkAddressEntry &entry) { return entry.ip() == local; });
334 if (Q_UNLIKELY(it == addressEntries.cend())) {
335 qCDebug(lcNetMon, "The address entry we were working with disappeared");
336 return false;
337 }
338 sameSubnet = remote.isInSubnet(local, it->prefixLength());
339 isLinkLocal = remote.isLinkLocal() && local.isLinkLocal();
340 remoteIsIPv6 = remote.protocol() == QAbstractSocket::IPv6Protocol;
341
342 return connectionEvents->setTarget(iface);
343}
344
345void QNetworkConnectionMonitorPrivate::setConnectivity(NLM_CONNECTIVITY newConnectivity)
346{
348 const bool reachable = q->isReachable();
349 connectivity = newConnectivity;
350 const bool newReachable = q->isReachable();
351 if (reachable != newReachable)
352 emit q->reachabilityChanged(newReachable);
353}
354
356{
357 Q_ASSERT(connectionEvents);
358 Q_ASSERT(!monitoring);
359 if (connectionEvents->startMonitoring())
360 monitoring = true;
361 return monitoring;
362}
363
365{
366 Q_ASSERT(connectionEvents);
367 Q_ASSERT(monitoring);
368 if (connectionEvents->stopMonitoring())
369 monitoring = false;
370}
371
374{
375}
376
378 const QHostAddress &remote)
380{
381 setTargets(local, remote);
382}
383
385
387{
388 if (isMonitoring()) {
389 qCDebug(lcNetMon, "Monitor is already active, call stopMonitoring() first");
390 return false;
391 }
392 if (local.isNull()) {
393 qCDebug(lcNetMon, "Invalid (null) local address, cannot create a reachability target");
394 return false;
395 }
396 // Silently return false for loopback addresses instead of printing warnings later
397 if (remote.isLoopback())
398 return false;
399
400 return d_func()->setTargets(local, remote);
401}
402
404{
406 if (isMonitoring()) {
407 qCDebug(lcNetMon, "Monitor is already active, call stopMonitoring() first");
408 return false;
409 }
410 return d->startMonitoring();
411}
412
414{
415 return d_func()->monitoring;
416}
417
419{
421 if (!isMonitoring()) {
422 qCDebug(lcNetMon, "stopMonitoring was called when not monitoring!");
423 return;
424 }
425 d->stopMonitoring();
426}
427
429{
431
432 const NLM_CONNECTIVITY RequiredSameSubnetIPv6 =
433 NLM_CONNECTIVITY(NLM_CONNECTIVITY_IPV6_SUBNET | NLM_CONNECTIVITY_IPV6_LOCALNETWORK
434 | NLM_CONNECTIVITY_IPV6_INTERNET);
435 const NLM_CONNECTIVITY RequiredSameSubnetIPv4 =
436 NLM_CONNECTIVITY(NLM_CONNECTIVITY_IPV4_SUBNET | NLM_CONNECTIVITY_IPV4_LOCALNETWORK
437 | NLM_CONNECTIVITY_IPV4_INTERNET);
438
439 NLM_CONNECTIVITY required;
440 if (d->isLinkLocal) {
441 required = NLM_CONNECTIVITY(
442 d->remoteIsIPv6 ? NLM_CONNECTIVITY_IPV6_NOTRAFFIC | RequiredSameSubnetIPv6
443 : NLM_CONNECTIVITY_IPV4_NOTRAFFIC | RequiredSameSubnetIPv4);
444 } else if (d->sameSubnet) {
445 required =
446 NLM_CONNECTIVITY(d->remoteIsIPv6 ? RequiredSameSubnetIPv6 : RequiredSameSubnetIPv4);
447
448 } else {
449 required = NLM_CONNECTIVITY(d->remoteIsIPv6 ? NLM_CONNECTIVITY_IPV6_INTERNET
450 : NLM_CONNECTIVITY_IPV4_INTERNET);
451 }
452
453 return d_func()->connectivity & required;
454}
455
457{
458 return true;
459}
460
static constexpr auto IPv6Protocol
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.
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:72
\inmodule QtCore
Definition qobject.h:103
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:818
#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...