blob: 1566e7f914c76b3d96886f5541b95418a9851e71 [file] [log] [blame]
/****************************************************************************
**
** 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 <QtCore/quuid.h>
#include <QtCore/qmetaobject.h>
#include <QtNetwork/qnetworkinterface.h>
#include <objbase.h>
#include <netlistmgr.h>
#include <wrl/client.h>
#include <wrl/wrappers/corewrappers.h>
#include <comdef.h>
#include <iphlpapi.h>
#include <algorithm>
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<typename T>
bool QueryInterfaceImpl(IUnknown *from, REFIID riid, void **ppvObject)
{
if (riid == __uuidof(T)) {
*ppvObject = static_cast<T *>(from);
from->AddRef();
return true;
}
return false;
}
QNetworkInterface getInterfaceFromHostAddress(const QHostAddress &local)
{
QList<QNetworkInterface> 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 : public INetworkConnectionEvents
{
public:
QNetworkConnectionEvents(QNetworkConnectionMonitorPrivate *monitor);
virtual ~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<INetworkConnection> getNetworkConnectionFromAdapterGuid(QUuid guid);
QUuid currentConnectionId{};
ComPtr<INetworkListManager> networkListManager;
ComPtr<IConnectionPoint> connectionPoint;
QNetworkConnectionMonitorPrivate *monitor = nullptr;
QAtomicInteger<ULONG> ref = 0;
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<QNetworkConnectionEvents> 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<IConnectionPointContainer> 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<INetworkConnection> QNetworkConnectionEvents::getNetworkConnectionFromAdapterGuid(QUuid guid)
{
ComPtr<IEnumNetworkConnections> connections;
auto hr = networkListManager->GetNetworkConnections(connections.GetAddressOf());
if (FAILED(hr)) {
qCWarning(lcNetMon) << "Failed to enumerate network connections:"
<< errorStringFromHResult(hr);
return nullptr;
}
ComPtr<INetworkConnection> 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<IUnknown>(this, riid, ppvObject)
|| QueryInterfaceImpl<INetworkConnectionEvents>(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<INetworkConnection> 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 = CoInitialize(nullptr);
if (FAILED(hr)) {
qCWarning(lcNetMon) << "Failed to initialize COM:" << errorStringFromHResult(hr);
comInitFailed = true;
return;
}
connectionEvents = new QNetworkConnectionEvents(this);
}
QNetworkConnectionMonitorPrivate::~QNetworkConnectionMonitorPrivate()
{
if (comInitFailed)
return;
if (monitoring)
stopMonitoring();
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 : public INetworkListManagerEvents
{
public:
QNetworkListManagerEvents(QNetworkStatusMonitorPrivate *monitor);
virtual ~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<INetworkListManager> networkListManager = nullptr;
ComPtr<IConnectionPoint> connectionPoint = nullptr;
QNetworkStatusMonitorPrivate *monitor = nullptr;
QAtomicInteger<ULONG> ref = 0;
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<QNetworkListManagerEvents> 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<IConnectionPointContainer> 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<IUnknown>(this, riid, ppvObject)
|| QueryInterfaceImpl<INetworkListManagerEvents>(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 = CoInitialize(nullptr);
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->isNetworkAccessible();
connectivity = newConnectivity;
const bool accessibility = q->isNetworkAccessible();
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::isNetworkAccessible()
{
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