blob: 9c0c4a55f481e10765646576b717fca5077bf3b4 [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 "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>
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;
};
QNetworkStatusMonitor::QNetworkStatusMonitor(QObject *parent)
: QObject(*new QNetworkStatusMonitorPrivate, parent)
{
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::isNetworkAccessible()
{
// This function is to be executed on the thread that created
// and uses 'this'.
Q_D(QNetworkStatusMonitor);
return d->isOnlineIpv4 || d->isOnlineIpv6;
}
bool QNetworkStatusMonitor::event(QEvent *event)
{
return QObject::event(event);
}
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<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