diff options
Diffstat (limited to 'src/network/kernel/qnetconmonitor_darwin.mm')
-rw-r--r-- | src/network/kernel/qnetconmonitor_darwin.mm | 434 |
1 files changed, 434 insertions, 0 deletions
diff --git a/src/network/kernel/qnetconmonitor_darwin.mm b/src/network/kernel/qnetconmonitor_darwin.mm new file mode 100644 index 0000000000..322c87cb4b --- /dev/null +++ b/src/network/kernel/qnetconmonitor_darwin.mm @@ -0,0 +1,434 @@ +/**************************************************************************** +** +** 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 "private/qnativesocketengine_p.h" +#include "private/qnetconmonitor_p.h" + +#include "private/qobject_p.h" + +#include <SystemConfiguration/SystemConfiguration.h> +#include <CoreFoundation/CoreFoundation.h> + +#include <netinet/in.h> + +#include <cstring> +#include <mutex> + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(lcNetMon, "qt.network.monitor"); + +namespace { + +class ReachabilityDispatchQueue +{ +public: + ReachabilityDispatchQueue() + { + queue = dispatch_queue_create("qt-network-reachability-queue", nullptr); + if (!queue) + qCWarning(lcNetMon, "Failed to create a dispatch queue for reachability probes"); + } + + ~ReachabilityDispatchQueue() + { + if (queue) + dispatch_release(queue); + } + + dispatch_queue_t data() const + { + return queue; + } + +private: + dispatch_queue_t queue = nullptr; + + Q_DISABLE_COPY_MOVE(ReachabilityDispatchQueue) +}; + +dispatch_queue_t qt_reachability_queue() +{ + static const ReachabilityDispatchQueue reachabilityQueue; + return reachabilityQueue.data(); +} + +qt_sockaddr qt_hostaddress_to_sockaddr(const QHostAddress &src) +{ + if (src.isNull()) + return {}; + + qt_sockaddr dst; + if (src.protocol() == QAbstractSocket::IPv4Protocol) { + dst.a4 = sockaddr_in{}; + dst.a4.sin_family = AF_INET; + dst.a4.sin_addr.s_addr = htonl(src.toIPv4Address()); + dst.a4.sin_len = sizeof(sockaddr_in); + } else if (src.protocol() == QAbstractSocket::IPv6Protocol) { + dst.a6 = sockaddr_in6{}; + dst.a6.sin6_family = AF_INET6; + dst.a6.sin6_len = sizeof(sockaddr_in6); + const Q_IPV6ADDR ipv6 = src.toIPv6Address(); + std::memcpy(&dst.a6.sin6_addr, &ipv6, sizeof ipv6); + } else { + Q_UNREACHABLE(); + } + + return dst; +} + +} // unnamed namespace + +class QNetworkConnectionMonitorPrivate : public QObjectPrivate +{ +public: + SCNetworkReachabilityRef probe = nullptr; + SCNetworkReachabilityFlags state = kSCNetworkReachabilityFlagsIsLocalAddress; + bool scheduled = false; + + void updateState(SCNetworkReachabilityFlags newState); + void reset(); + bool isReachable() const; + + static void probeCallback(SCNetworkReachabilityRef probe, SCNetworkReachabilityFlags flags, void *info); + + Q_DECLARE_PUBLIC(QNetworkConnectionMonitor) +}; + +void QNetworkConnectionMonitorPrivate::updateState(SCNetworkReachabilityFlags newState) +{ + // To be executed only on the reachability queue. + Q_Q(QNetworkConnectionMonitor); + + // NETMONTODO: for now, 'online' for us means kSCNetworkReachabilityFlagsReachable + // is set. There are more possible flags that require more tests/some special + // setup. So in future this part and related can change/be extended. + const bool wasReachable = isReachable(); + state = newState; + if (wasReachable != isReachable()) + emit q->reachabilityChanged(isReachable()); +} + +void QNetworkConnectionMonitorPrivate::reset() +{ + if (probe) { + CFRelease(probe); + probe = nullptr; + } + + state = kSCNetworkReachabilityFlagsIsLocalAddress; + scheduled = false; +} + +bool QNetworkConnectionMonitorPrivate::isReachable() const +{ + return !!(state & kSCNetworkReachabilityFlagsReachable); +} + +void QNetworkConnectionMonitorPrivate::probeCallback(SCNetworkReachabilityRef probe, SCNetworkReachabilityFlags flags, void *info) +{ + // To be executed only on the reachability queue. + Q_UNUSED(probe); + + auto monitorPrivate = static_cast<QNetworkConnectionMonitorPrivate *>(info); + Q_ASSERT(monitorPrivate); + monitorPrivate->updateState(flags); +} + +QNetworkConnectionMonitor::QNetworkConnectionMonitor() + : QObject(*new QNetworkConnectionMonitorPrivate) +{ +} + +QNetworkConnectionMonitor::QNetworkConnectionMonitor(const QHostAddress &local, const QHostAddress &remote) + : QObject(*new QNetworkConnectionMonitorPrivate) +{ + setTargets(local, remote); +} + +QNetworkConnectionMonitor::~QNetworkConnectionMonitor() +{ + Q_D(QNetworkConnectionMonitor); + + stopMonitoring(); + d->reset(); +} + +bool QNetworkConnectionMonitor::setTargets(const QHostAddress &local, const QHostAddress &remote) +{ + Q_D(QNetworkConnectionMonitor); + + 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; + } + + // Clear the old target if needed: + d->reset(); + + qt_sockaddr client = qt_hostaddress_to_sockaddr(local); + if (remote.isNull()) { + // That's a special case our QNetworkStatusMonitor is using (AnyIpv4/6 address to check an overall status). + d->probe = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, reinterpret_cast<sockaddr *>(&client)); + } else { + qt_sockaddr target = qt_hostaddress_to_sockaddr(remote); + d->probe = SCNetworkReachabilityCreateWithAddressPair(kCFAllocatorDefault, + reinterpret_cast<sockaddr *>(&client), + reinterpret_cast<sockaddr *>(&target)); + } + + if (d->probe) { + // Let's read the initial state so that callback coming later can + // see a difference. Ignore errors though. + SCNetworkReachabilityGetFlags(d->probe, &d->state); + }else { + qCWarning(lcNetMon, "Failed to create network reachability probe"); + return false; + } + + return true; +} + +bool QNetworkConnectionMonitor::startMonitoring() +{ + Q_D(QNetworkConnectionMonitor); + + if (isMonitoring()) { + qCWarning(lcNetMon, "Monitor is already active, call stopMonitoring() first"); + return false; + } + + if (!d->probe) { + qCWarning(lcNetMon, "Can not start monitoring, set targets first"); + return false; + } + + auto queue = qt_reachability_queue(); + if (!queue) { + qWarning(lcNetMon, "Failed to create a dispatch queue to schedule a probe on"); + return false; + } + + SCNetworkReachabilityContext context = {}; + context.info = d; + if (!SCNetworkReachabilitySetCallback(d->probe, QNetworkConnectionMonitorPrivate::probeCallback, &context)) { + qWarning(lcNetMon, "Failed to set a reachability callback"); + return false; + } + + + if (!SCNetworkReachabilitySetDispatchQueue(d->probe, queue)) { + qWarning(lcNetMon, "Failed to schedule a reachability callback on a queue"); + return false; + } + + return d->scheduled = true; +} + +bool QNetworkConnectionMonitor::isMonitoring() const +{ + Q_D(const QNetworkConnectionMonitor); + + return d->scheduled; +} + +void QNetworkConnectionMonitor::stopMonitoring() +{ + Q_D(QNetworkConnectionMonitor); + + if (d->scheduled) { + Q_ASSERT(d->probe); + SCNetworkReachabilitySetDispatchQueue(d->probe, nullptr); + SCNetworkReachabilitySetCallback(d->probe, nullptr, nullptr); + d->scheduled = false; + } +} + +bool QNetworkConnectionMonitor::isReachable() +{ + Q_D(QNetworkConnectionMonitor); + + if (isMonitoring()) { + qCWarning(lcNetMon, "Calling isReachable() is unsafe after the monitoring started"); + return false; + } + + if (!d->probe) { + qCWarning(lcNetMon, "Reachability is unknown, set the target first"); + return false; + } + + return d->isReachable(); +} + +class QNetworkStatusMonitorPrivate : public QObjectPrivate +{ +public: + QNetworkConnectionMonitor ipv4Probe; + bool isOnlineIpv4 = false; + QNetworkConnectionMonitor ipv6Probe; + bool isOnlineIpv6 = false; + + static bool enabled; + static void readEnv(); +}; + +bool QNetworkStatusMonitorPrivate::enabled = false; + +void QNetworkStatusMonitorPrivate::readEnv() +{ + bool envOk = false; + const int env = qEnvironmentVariableIntValue("QT_USE_NETWORK_MONITOR", &envOk); + enabled = envOk && env > 0; +} + +QNetworkStatusMonitor::QNetworkStatusMonitor() + : QObject(*new QNetworkStatusMonitorPrivate) +{ + Q_D(QNetworkStatusMonitor); + + if (d->ipv4Probe.setTargets(QHostAddress::AnyIPv4, {})) { + // We manage to create SCNetworkReachabilityRef for IPv4, let's + // read the last known state then! + d->isOnlineIpv4 = d->ipv4Probe.isReachable(); + } + + if (d->ipv6Probe.setTargets(QHostAddress::AnyIPv6, {})) { + // We manage to create SCNetworkReachability ref for IPv6, let's + // read the last known state then! + d->isOnlineIpv6 = d->ipv6Probe.isReachable(); + } + + + connect(&d->ipv4Probe, &QNetworkConnectionMonitor::reachabilityChanged, this, + &QNetworkStatusMonitor::reachabilityChanged, Qt::QueuedConnection); + connect(&d->ipv6Probe, &QNetworkConnectionMonitor::reachabilityChanged, this, + &QNetworkStatusMonitor::reachabilityChanged, Qt::QueuedConnection); +} + +QNetworkStatusMonitor::~QNetworkStatusMonitor() +{ + Q_D(QNetworkStatusMonitor); + + d->ipv4Probe.disconnect(); + d->ipv4Probe.stopMonitoring(); + d->ipv6Probe.disconnect(); + d->ipv6Probe.stopMonitoring(); +} + +bool QNetworkStatusMonitor::start() +{ + Q_D(QNetworkStatusMonitor); + + if (isMonitoring()) { + qCWarning(lcNetMon, "Network status monitor is already active"); + return true; + } + + d->ipv4Probe.startMonitoring(); + d->ipv6Probe.startMonitoring(); + + return isMonitoring(); +} + +void QNetworkStatusMonitor::stop() +{ + Q_D(QNetworkStatusMonitor); + + if (d->ipv4Probe.isMonitoring()) + d->ipv4Probe.stopMonitoring(); + if (d->ipv6Probe.isMonitoring()) + d->ipv6Probe.stopMonitoring(); +} + +bool QNetworkStatusMonitor::isMonitoring() const +{ + Q_D(const QNetworkStatusMonitor); + + return d->ipv4Probe.isMonitoring() || d->ipv6Probe.isMonitoring(); +} + +bool QNetworkStatusMonitor::isNetworkAccesible() +{ + // This function is to be executed on the thread that created + // and uses 'this'. + Q_D(QNetworkStatusMonitor); + + return d->isOnlineIpv4 || d->isOnlineIpv6; +} + +bool QNetworkStatusMonitor::isEnabled() +{ + static std::once_flag envRead = {}; + std::call_once(envRead, QNetworkStatusMonitorPrivate::readEnv); + return QNetworkStatusMonitorPrivate::enabled; +} + +void QNetworkStatusMonitor::reachabilityChanged(bool online) +{ + // This function is executed on the thread that created/uses 'this', + // not on the reachability queue. + Q_D(QNetworkStatusMonitor); + + auto probe = qobject_cast<QNetworkConnectionMonitor *>(sender()); + if (!probe) + return; + + const bool isIpv4 = probe == &d->ipv4Probe; + bool &probeOnline = isIpv4 ? d->isOnlineIpv4 : d->isOnlineIpv6; + bool otherOnline = isIpv4 ? d->isOnlineIpv6 : d->isOnlineIpv4; + + if (probeOnline == online) { + // We knew this already? + return; + } + + probeOnline = online; + if (!otherOnline) { + // We either just lost or got a network access. + emit onlineStateChanged(probeOnline); + } +} + +QT_END_NAMESPACE |