/**************************************************************************** ** ** 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 #include #include #include 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(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(&client)); } else { qt_sockaddr target = qt_hostaddress_to_sockaddr(remote); d->probe = SCNetworkReachabilityCreateWithAddressPair(kCFAllocatorDefault, reinterpret_cast(&client), reinterpret_cast(&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; }; 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() { return true; } 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(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