From dc431b7e8548e97d19770285a6160ecfa8874815 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A5rten=20Nordheim?= Date: Fri, 12 Apr 2019 15:46:35 +0200 Subject: Introduce QNetwork(Connection|Status)Monitor to Windows Currently not available for mingw because it's missing all the interfaces. But should start working once it has them. Change-Id: I231e8b69e008c5300a83087fe9cd071acd0687cb Reviewed-by: Timur Pocheptsov --- src/network/kernel/qnetconmonitor_win.cpp | 710 ++++++++++++++++++++++++++++++ 1 file changed, 710 insertions(+) create mode 100644 src/network/kernel/qnetconmonitor_win.cpp (limited to 'src/network/kernel/qnetconmonitor_win.cpp') diff --git a/src/network/kernel/qnetconmonitor_win.cpp b/src/network/kernel/qnetconmonitor_win.cpp new file mode 100644 index 0000000000..2eab0a062f --- /dev/null +++ b/src/network/kernel/qnetconmonitor_win.cpp @@ -0,0 +1,710 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qnetconmonitor_p.h" + +#include "private/qobject_p.h" + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include + +using namespace Microsoft::WRL; + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(lcNetMon, "qt.network.monitor"); + +namespace { +QString errorStringFromHResult(HRESULT hr) +{ + _com_error error(hr); + return QString::fromWCharArray(error.ErrorMessage()); +} + +template +bool QueryInterfaceImpl(IUnknown *from, REFIID riid, void **ppvObject) +{ + if (riid == __uuidof(T)) { + *ppvObject = static_cast(from); + from->AddRef(); + return true; + } + return false; +} + +QNetworkInterface getInterfaceFromHostAddress(const QHostAddress &local) +{ + QList interfaces = QNetworkInterface::allInterfaces(); + auto it = std::find_if( + interfaces.cbegin(), interfaces.cend(), [&local](const QNetworkInterface &iface) { + const auto &entries = iface.addressEntries(); + return std::any_of(entries.cbegin(), entries.cend(), + [&local](const QNetworkAddressEntry &entry) { + return entry.ip().isEqual(local, + QHostAddress::TolerantConversion); + }); + }); + if (it == interfaces.cend()) { + qCWarning(lcNetMon, "Could not find the interface for the local address."); + return {}; + } + return *it; +} +} // anonymous namespace + +class QNetworkConnectionEvents final : public INetworkConnectionEvents +{ +public: + QNetworkConnectionEvents(QNetworkConnectionMonitorPrivate *monitor); + ~QNetworkConnectionEvents(); + + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObject) override; + + ULONG STDMETHODCALLTYPE AddRef() override { return ++ref; } + ULONG STDMETHODCALLTYPE Release() override + { + if (--ref == 0) { + delete this; + return 0; + } + return ref; + } + + HRESULT STDMETHODCALLTYPE + NetworkConnectionConnectivityChanged(GUID connectionId, NLM_CONNECTIVITY connectivity) override; + HRESULT STDMETHODCALLTYPE NetworkConnectionPropertyChanged( + GUID connectionId, NLM_CONNECTION_PROPERTY_CHANGE flags) override; + + Q_REQUIRED_RESULT + bool setTarget(const QNetworkInterface &iface); + Q_REQUIRED_RESULT + bool startMonitoring(); + Q_REQUIRED_RESULT + bool stopMonitoring(); + +private: + ComPtr getNetworkConnectionFromAdapterGuid(QUuid guid); + + QUuid currentConnectionId{}; + + ComPtr networkListManager; + ComPtr connectionPoint; + + QNetworkConnectionMonitorPrivate *monitor = nullptr; + + QAtomicInteger ref = 1; // start at 1 for our own initial reference + DWORD cookie = 0; +}; + +class QNetworkConnectionMonitorPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QNetworkConnectionMonitor); + +public: + QNetworkConnectionMonitorPrivate(); + ~QNetworkConnectionMonitorPrivate(); + + Q_REQUIRED_RESULT + bool setTargets(const QHostAddress &local, const QHostAddress &remote); + Q_REQUIRED_RESULT + bool startMonitoring(); + void stopMonitoring(); + + void setConnectivity(NLM_CONNECTIVITY newConnectivity); + +private: + ComPtr connectionEvents; + // We can assume we have access to internet/subnet when this class is created because + // connection has already been established to the peer: + NLM_CONNECTIVITY connectivity = + NLM_CONNECTIVITY(NLM_CONNECTIVITY_IPV4_INTERNET | NLM_CONNECTIVITY_IPV6_INTERNET + | NLM_CONNECTIVITY_IPV4_SUBNET | NLM_CONNECTIVITY_IPV6_SUBNET); + + bool sameSubnet = false; + bool monitoring = false; + bool comInitFailed = false; + bool remoteIsIPv6 = false; +}; + +QNetworkConnectionEvents::QNetworkConnectionEvents(QNetworkConnectionMonitorPrivate *monitor) + : monitor(monitor) +{ + auto hr = CoCreateInstance(CLSID_NetworkListManager, nullptr, CLSCTX_INPROC_SERVER, + IID_INetworkListManager, &networkListManager); + if (FAILED(hr)) { + qCWarning(lcNetMon) << "Could not get a NetworkListManager instance:" + << errorStringFromHResult(hr); + return; + } + + ComPtr connectionPointContainer; + hr = networkListManager.As(&connectionPointContainer); + if (SUCCEEDED(hr)) { + hr = connectionPointContainer->FindConnectionPoint(IID_INetworkConnectionEvents, + &connectionPoint); + } + if (FAILED(hr)) { + qCWarning(lcNetMon) << "Failed to get connection point for network events:" + << errorStringFromHResult(hr); + } +} + +QNetworkConnectionEvents::~QNetworkConnectionEvents() +{ + Q_ASSERT(ref == 0); +} + +ComPtr QNetworkConnectionEvents::getNetworkConnectionFromAdapterGuid(QUuid guid) +{ + ComPtr connections; + auto hr = networkListManager->GetNetworkConnections(connections.GetAddressOf()); + if (FAILED(hr)) { + qCWarning(lcNetMon) << "Failed to enumerate network connections:" + << errorStringFromHResult(hr); + return nullptr; + } + ComPtr connection = nullptr; + do { + hr = connections->Next(1, connection.GetAddressOf(), nullptr); + if (FAILED(hr)) { + qCWarning(lcNetMon) << "Failed to get next network connection in enumeration:" + << errorStringFromHResult(hr); + break; + } + if (connection) { + GUID adapterId; + hr = connection->GetAdapterId(&adapterId); + if (FAILED(hr)) { + qCWarning(lcNetMon) << "Failed to get adapter ID from network connection:" + << errorStringFromHResult(hr); + continue; + } + if (guid == adapterId) + return connection; + } + } while (connection); + return nullptr; +} + +HRESULT STDMETHODCALLTYPE QNetworkConnectionEvents::QueryInterface(REFIID riid, void **ppvObject) +{ + if (!ppvObject) + return E_INVALIDARG; + + return QueryInterfaceImpl(this, riid, ppvObject) + || QueryInterfaceImpl(this, riid, ppvObject) + ? S_OK + : E_NOINTERFACE; +} + +HRESULT STDMETHODCALLTYPE QNetworkConnectionEvents::NetworkConnectionConnectivityChanged( + GUID connectionId, NLM_CONNECTIVITY newConnectivity) +{ + // This function is run on a different thread than 'monitor' is created on, so we need to run + // it on that thread + QMetaObject::invokeMethod(monitor->q_ptr, + [this, connectionId, newConnectivity, monitor = this->monitor]() { + if (connectionId == currentConnectionId) + monitor->setConnectivity(newConnectivity); + }, + Qt::QueuedConnection); + return S_OK; +} + +HRESULT STDMETHODCALLTYPE QNetworkConnectionEvents::NetworkConnectionPropertyChanged( + GUID connectionId, NLM_CONNECTION_PROPERTY_CHANGE flags) +{ + Q_UNUSED(connectionId); + Q_UNUSED(flags); + return E_NOTIMPL; +} + +bool QNetworkConnectionEvents::setTarget(const QNetworkInterface &iface) +{ + // Unset this in case it's already set to something + currentConnectionId = QUuid{}; + + NET_LUID luid; + if (ConvertInterfaceIndexToLuid(iface.index(), &luid) != NO_ERROR) { + qCWarning(lcNetMon, "Could not get the LUID for the interface."); + return false; + } + GUID guid; + if (ConvertInterfaceLuidToGuid(&luid, &guid) != NO_ERROR) { + qCWarning(lcNetMon, "Could not get the GUID for the interface."); + return false; + } + ComPtr connection = getNetworkConnectionFromAdapterGuid(guid); + if (!connection) { + qCWarning(lcNetMon, "Could not get the INetworkConnection instance for the adapter GUID."); + return false; + } + auto hr = connection->GetConnectionId(&guid); + if (FAILED(hr)) { + qCWarning(lcNetMon) << "Failed to get the connection's GUID:" << errorStringFromHResult(hr); + return false; + } + currentConnectionId = guid; + + return true; +} + +bool QNetworkConnectionEvents::startMonitoring() +{ + if (currentConnectionId.isNull()) { + qCWarning(lcNetMon, "Can not start monitoring, set targets first"); + return false; + } + if (!connectionPoint) { + qCWarning(lcNetMon, + "We don't have the connection point, cannot start listening to events!"); + return false; + } + + auto hr = connectionPoint->Advise(this, &cookie); + if (FAILED(hr)) { + qCWarning(lcNetMon) << "Failed to subscribe to network connectivity events:" + << errorStringFromHResult(hr); + return false; + } + return true; +} + +bool QNetworkConnectionEvents::stopMonitoring() +{ + auto hr = connectionPoint->Unadvise(cookie); + if (FAILED(hr)) { + qCWarning(lcNetMon) << "Failed to unsubscribe from network connection events:" + << errorStringFromHResult(hr); + return false; + } + cookie = 0; + currentConnectionId = QUuid{}; + return true; +} + +QNetworkConnectionMonitorPrivate::QNetworkConnectionMonitorPrivate() +{ + auto hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED); + if (FAILED(hr)) { + qCWarning(lcNetMon) << "Failed to initialize COM:" << errorStringFromHResult(hr); + comInitFailed = true; + return; + } + + connectionEvents = new QNetworkConnectionEvents(this); +} + +QNetworkConnectionMonitorPrivate::~QNetworkConnectionMonitorPrivate() +{ + if (comInitFailed) + return; + connectionEvents.Reset(); + CoUninitialize(); +} + +bool QNetworkConnectionMonitorPrivate::setTargets(const QHostAddress &local, + const QHostAddress &remote) +{ + if (comInitFailed) + return false; + + QNetworkInterface iface = getInterfaceFromHostAddress(local); + if (!iface.isValid()) + return false; + const auto &addressEntries = iface.addressEntries(); + auto it = std::find_if( + addressEntries.cbegin(), addressEntries.cend(), + [&local](const QNetworkAddressEntry &entry) { return entry.ip() == local; }); + if (Q_UNLIKELY(it == addressEntries.cend())) { + qCWarning(lcNetMon, "The address entry we were working with disappeared"); + return false; + } + sameSubnet = remote.isInSubnet(local, it->prefixLength()); + remoteIsIPv6 = remote.protocol() == QAbstractSocket::IPv6Protocol; + + return connectionEvents->setTarget(iface); +} + +void QNetworkConnectionMonitorPrivate::setConnectivity(NLM_CONNECTIVITY newConnectivity) +{ + Q_Q(QNetworkConnectionMonitor); + const bool reachable = q->isReachable(); + connectivity = newConnectivity; + const bool newReachable = q->isReachable(); + if (reachable != newReachable) + emit q->reachabilityChanged(newReachable); +} + +bool QNetworkConnectionMonitorPrivate::startMonitoring() +{ + Q_ASSERT(connectionEvents); + Q_ASSERT(!monitoring); + if (connectionEvents->startMonitoring()) + monitoring = true; + return monitoring; +} + +void QNetworkConnectionMonitorPrivate::stopMonitoring() +{ + Q_ASSERT(connectionEvents); + Q_ASSERT(monitoring); + if (connectionEvents->stopMonitoring()) + monitoring = false; +} + +QNetworkConnectionMonitor::QNetworkConnectionMonitor() + : QObject(*new QNetworkConnectionMonitorPrivate) +{ +} + +QNetworkConnectionMonitor::QNetworkConnectionMonitor(const QHostAddress &local, + const QHostAddress &remote) + : QObject(*new QNetworkConnectionMonitorPrivate) +{ + setTargets(local, remote); +} + +QNetworkConnectionMonitor::~QNetworkConnectionMonitor() = default; + +bool QNetworkConnectionMonitor::setTargets(const QHostAddress &local, const QHostAddress &remote) +{ + if (isMonitoring()) { + qCWarning(lcNetMon, "Monitor is already active, call stopMonitoring() first"); + return false; + } + if (local.isNull()) { + qCWarning(lcNetMon, "Invalid (null) local address, cannot create a reachability target"); + return false; + } + // Silently return false for loopback addresses instead of printing warnings later + if (remote.isLoopback()) + return false; + + return d_func()->setTargets(local, remote); +} + +bool QNetworkConnectionMonitor::startMonitoring() +{ + Q_D(QNetworkConnectionMonitor); + if (isMonitoring()) { + qCWarning(lcNetMon, "Monitor is already active, call stopMonitoring() first"); + return false; + } + return d->startMonitoring(); +} + +bool QNetworkConnectionMonitor::isMonitoring() const +{ + return d_func()->monitoring; +} + +void QNetworkConnectionMonitor::stopMonitoring() +{ + Q_D(QNetworkConnectionMonitor); + if (!isMonitoring()) { + qCWarning(lcNetMon, "stopMonitoring was called when not monitoring!"); + return; + } + d->stopMonitoring(); +} + +bool QNetworkConnectionMonitor::isReachable() +{ + Q_D(QNetworkConnectionMonitor); + NLM_CONNECTIVITY required = d->sameSubnet + ? (d->remoteIsIPv6 ? NLM_CONNECTIVITY_IPV6_SUBNET : NLM_CONNECTIVITY_IPV4_SUBNET) + : (d->remoteIsIPv6 ? NLM_CONNECTIVITY_IPV6_INTERNET : NLM_CONNECTIVITY_IPV4_INTERNET); + return d_func()->connectivity & required; +} + +class QNetworkListManagerEvents final : public INetworkListManagerEvents +{ +public: + QNetworkListManagerEvents(QNetworkStatusMonitorPrivate *monitor); + ~QNetworkListManagerEvents(); + + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObject) override; + + ULONG STDMETHODCALLTYPE AddRef() override { return ++ref; } + ULONG STDMETHODCALLTYPE Release() override + { + if (--ref == 0) { + delete this; + return 0; + } + return ref; + } + + HRESULT STDMETHODCALLTYPE ConnectivityChanged(NLM_CONNECTIVITY newConnectivity) override; + + Q_REQUIRED_RESULT + bool start(); + Q_REQUIRED_RESULT + bool stop(); + +private: + ComPtr networkListManager = nullptr; + ComPtr connectionPoint = nullptr; + + QNetworkStatusMonitorPrivate *monitor = nullptr; + + QAtomicInteger ref = 1; // start at 1 for our own initial reference + DWORD cookie = 0; +}; + +class QNetworkStatusMonitorPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QNetworkStatusMonitor); + +public: + QNetworkStatusMonitorPrivate(); + ~QNetworkStatusMonitorPrivate(); + + Q_REQUIRED_RESULT + bool start(); + void stop(); + + void setConnectivity(NLM_CONNECTIVITY newConnectivity); + +private: + friend class QNetworkListManagerEvents; + + ComPtr managerEvents; + NLM_CONNECTIVITY connectivity = NLM_CONNECTIVITY_DISCONNECTED; + + bool monitoring = false; + bool comInitFailed = false; +}; + +QNetworkListManagerEvents::QNetworkListManagerEvents(QNetworkStatusMonitorPrivate *monitor) + : monitor(monitor) +{ + auto hr = CoCreateInstance(CLSID_NetworkListManager, nullptr, CLSCTX_INPROC_SERVER, + IID_INetworkListManager, &networkListManager); + if (FAILED(hr)) { + qCWarning(lcNetMon) << "Could not get a NetworkListManager instance:" + << errorStringFromHResult(hr); + return; + } + + // Set initial connectivity + hr = networkListManager->GetConnectivity(&monitor->connectivity); + if (FAILED(hr)) + qCWarning(lcNetMon) << "Could not get connectivity:" << errorStringFromHResult(hr); + + ComPtr connectionPointContainer; + hr = networkListManager.As(&connectionPointContainer); + if (SUCCEEDED(hr)) { + hr = connectionPointContainer->FindConnectionPoint(IID_INetworkListManagerEvents, + &connectionPoint); + } + if (FAILED(hr)) { + qCWarning(lcNetMon) << "Failed to get connection point for network list manager events:" + << errorStringFromHResult(hr); + } +} + +QNetworkListManagerEvents::~QNetworkListManagerEvents() +{ + Q_ASSERT(ref == 0); +} + +HRESULT STDMETHODCALLTYPE QNetworkListManagerEvents::QueryInterface(REFIID riid, void **ppvObject) +{ + if (!ppvObject) + return E_INVALIDARG; + + return QueryInterfaceImpl(this, riid, ppvObject) + || QueryInterfaceImpl(this, riid, ppvObject) + ? S_OK + : E_NOINTERFACE; +} + +HRESULT STDMETHODCALLTYPE +QNetworkListManagerEvents::ConnectivityChanged(NLM_CONNECTIVITY newConnectivity) +{ + // This function is run on a different thread than 'monitor' is created on, so we need to run + // it on that thread + QMetaObject::invokeMethod(monitor->q_ptr, + [newConnectivity, monitor = this->monitor]() { + monitor->setConnectivity(newConnectivity); + }, + Qt::QueuedConnection); + return S_OK; +} + +bool QNetworkListManagerEvents::start() +{ + if (!connectionPoint) { + qCWarning(lcNetMon, "Initialization failed, can't start!"); + return false; + } + auto hr = connectionPoint->Advise(this, &cookie); + if (FAILED(hr)) { + qCWarning(lcNetMon) << "Failed to subscribe to network connectivity events:" + << errorStringFromHResult(hr); + return false; + } + return true; +} + +bool QNetworkListManagerEvents::stop() +{ + Q_ASSERT(connectionPoint); + auto hr = connectionPoint->Unadvise(cookie); + if (FAILED(hr)) { + qCWarning(lcNetMon) << "Failed to unsubscribe from network connectivity events:" + << errorStringFromHResult(hr); + return false; + } + cookie = 0; + return true; +} + +QNetworkStatusMonitorPrivate::QNetworkStatusMonitorPrivate() +{ + auto hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED); + if (FAILED(hr)) { + qCWarning(lcNetMon) << "Failed to initialize COM:" << errorStringFromHResult(hr); + comInitFailed = true; + return; + } + managerEvents = new QNetworkListManagerEvents(this); +} + +QNetworkStatusMonitorPrivate::~QNetworkStatusMonitorPrivate() +{ + if (comInitFailed) + return; + if (monitoring) + stop(); + managerEvents.Reset(); + CoUninitialize(); +} + +void QNetworkStatusMonitorPrivate::setConnectivity(NLM_CONNECTIVITY newConnectivity) +{ + Q_Q(QNetworkStatusMonitor); + + const bool oldAccessibility = q->isNetworkAccesible(); + connectivity = newConnectivity; + const bool accessibility = q->isNetworkAccesible(); + if (oldAccessibility != accessibility) + emit q->onlineStateChanged(accessibility); +} + +bool QNetworkStatusMonitorPrivate::start() +{ + if (comInitFailed) + return false; + Q_ASSERT(managerEvents); + Q_ASSERT(!monitoring); + if (managerEvents->start()) + monitoring = true; + return monitoring; +} + +void QNetworkStatusMonitorPrivate::stop() +{ + Q_ASSERT(managerEvents); + Q_ASSERT(monitoring); + if (managerEvents->stop()) + monitoring = false; +} + +QNetworkStatusMonitor::QNetworkStatusMonitor() : QObject(*new QNetworkStatusMonitorPrivate) {} + +QNetworkStatusMonitor::~QNetworkStatusMonitor() {} + +bool QNetworkStatusMonitor::start() +{ + if (isMonitoring()) { + qCWarning(lcNetMon, "Monitor is already active, call stopMonitoring() first"); + return false; + } + + return d_func()->start(); +} + +void QNetworkStatusMonitor::stop() +{ + if (!isMonitoring()) { + qCWarning(lcNetMon, "stopMonitoring was called when not monitoring!"); + return; + } + + d_func()->stop(); +} + +bool QNetworkStatusMonitor::isMonitoring() const +{ + return d_func()->monitoring; +} + +bool QNetworkStatusMonitor::isNetworkAccesible() +{ + return d_func()->connectivity + & (NLM_CONNECTIVITY_IPV4_INTERNET | NLM_CONNECTIVITY_IPV6_INTERNET + | NLM_CONNECTIVITY_IPV4_SUBNET | NLM_CONNECTIVITY_IPV6_SUBNET); +} + +bool QNetworkStatusMonitor::isEnabled() +{ + return true; +} + +void QNetworkStatusMonitor::reachabilityChanged(bool online) +{ + Q_UNUSED(online); + Q_UNREACHABLE(); +} + +QT_END_NAMESPACE -- cgit v1.2.3