1/**************************************************************************** 2** 3** Copyright (C) 2019 The Qt Company Ltd. 4** Contact: https://www.qt.io/licensing/ 5** 6** This file is part of the QtNetwork module of the Qt Toolkit. 7** 8** $QT_BEGIN_LICENSE:LGPL$ 9** Commercial License Usage 10** Licensees holding valid commercial Qt licenses may use this file in 11** accordance with the commercial license agreement provided with the 12** Software or, alternatively, in accordance with the terms contained in 13** a written agreement between you and The Qt Company. For licensing terms 14** and conditions see https://www.qt.io/terms-conditions. For further 15** information use the contact form at https://www.qt.io/contact-us. 16** 17** GNU Lesser General Public License Usage 18** Alternatively, this file may be used under the terms of the GNU Lesser 19** General Public License version 3 as published by the Free Software 20** Foundation and appearing in the file LICENSE.LGPL3 included in the 21** packaging of this file. Please review the following information to 22** ensure the GNU Lesser General Public License version 3 requirements 23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. 24** 25** GNU General Public License Usage 26** Alternatively, this file may be used under the terms of the GNU 27** General Public License version 2.0 or (at your option) the GNU General 28** Public license version 3 or any later version approved by the KDE Free 29** Qt Foundation. The licenses are as published by the Free Software 30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 31** included in the packaging of this file. Please review the following 32** information to ensure the GNU General Public License requirements will 33** be met: https://www.gnu.org/licenses/gpl-2.0.html and 34** https://www.gnu.org/licenses/gpl-3.0.html. 35** 36** $QT_END_LICENSE$ 37** 38****************************************************************************/ 39 40#include "private/qnativesocketengine_p.h" 41#include "private/qnetconmonitor_p.h" 42 43#include "private/qobject_p.h" 44 45#include <SystemConfiguration/SystemConfiguration.h> 46#include <CoreFoundation/CoreFoundation.h> 47 48#include <netinet/in.h> 49 50#include <cstring> 51 52QT_BEGIN_NAMESPACE 53 54Q_LOGGING_CATEGORY(lcNetMon, "qt.network.monitor"); 55 56namespace { 57 58class ReachabilityDispatchQueue 59{ 60public: 61 ReachabilityDispatchQueue() 62 { 63 queue = dispatch_queue_create("qt-network-reachability-queue", nullptr); 64 if (!queue) 65 qCWarning(lcNetMon, "Failed to create a dispatch queue for reachability probes"); 66 } 67 68 ~ReachabilityDispatchQueue() 69 { 70 if (queue) 71 dispatch_release(queue); 72 } 73 74 dispatch_queue_t data() const 75 { 76 return queue; 77 } 78 79private: 80 dispatch_queue_t queue = nullptr; 81 82 Q_DISABLE_COPY_MOVE(ReachabilityDispatchQueue) 83}; 84 85dispatch_queue_t qt_reachability_queue() 86{ 87 static const ReachabilityDispatchQueue reachabilityQueue; 88 return reachabilityQueue.data(); 89} 90 91qt_sockaddr qt_hostaddress_to_sockaddr(const QHostAddress &src) 92{ 93 if (src.isNull()) 94 return {}; 95 96 qt_sockaddr dst; 97 if (src.protocol() == QAbstractSocket::IPv4Protocol) { 98 dst.a4 = sockaddr_in{}; 99 dst.a4.sin_family = AF_INET; 100 dst.a4.sin_addr.s_addr = htonl(src.toIPv4Address()); 101 dst.a4.sin_len = sizeof(sockaddr_in); 102 } else if (src.protocol() == QAbstractSocket::IPv6Protocol) { 103 dst.a6 = sockaddr_in6{}; 104 dst.a6.sin6_family = AF_INET6; 105 dst.a6.sin6_len = sizeof(sockaddr_in6); 106 const Q_IPV6ADDR ipv6 = src.toIPv6Address(); 107 std::memcpy(&dst.a6.sin6_addr, &ipv6, sizeof ipv6); 108 } else { 109 Q_UNREACHABLE(); 110 } 111 112 return dst; 113} 114 115} // unnamed namespace 116 117class QNetworkConnectionMonitorPrivate : public QObjectPrivate 118{ 119public: 120 SCNetworkReachabilityRef probe = nullptr; 121 SCNetworkReachabilityFlags state = kSCNetworkReachabilityFlagsIsLocalAddress; 122 bool scheduled = false; 123 124 void updateState(SCNetworkReachabilityFlags newState); 125 void reset(); 126 bool isReachable() const; 127 128 static void probeCallback(SCNetworkReachabilityRef probe, SCNetworkReachabilityFlags flags, void *info); 129 130 Q_DECLARE_PUBLIC(QNetworkConnectionMonitor) 131}; 132 133void QNetworkConnectionMonitorPrivate::updateState(SCNetworkReachabilityFlags newState) 134{ 135 // To be executed only on the reachability queue. 136 Q_Q(QNetworkConnectionMonitor); 137 138 // NETMONTODO: for now, 'online' for us means kSCNetworkReachabilityFlagsReachable 139 // is set. There are more possible flags that require more tests/some special 140 // setup. So in future this part and related can change/be extended. 141 const bool wasReachable = isReachable(); 142 state = newState; 143 if (wasReachable != isReachable()) 144 emit q->reachabilityChanged(isReachable()); 145} 146 147void QNetworkConnectionMonitorPrivate::reset() 148{ 149 if (probe) { 150 CFRelease(probe); 151 probe = nullptr; 152 } 153 154 state = kSCNetworkReachabilityFlagsIsLocalAddress; 155 scheduled = false; 156} 157 158bool QNetworkConnectionMonitorPrivate::isReachable() const 159{ 160 return !!(state & kSCNetworkReachabilityFlagsReachable); 161} 162 163void QNetworkConnectionMonitorPrivate::probeCallback(SCNetworkReachabilityRef probe, SCNetworkReachabilityFlags flags, void *info) 164{ 165 // To be executed only on the reachability queue. 166 Q_UNUSED(probe); 167 168 auto monitorPrivate = static_cast<QNetworkConnectionMonitorPrivate *>(info); 169 Q_ASSERT(monitorPrivate); 170 monitorPrivate->updateState(flags); 171} 172 173QNetworkConnectionMonitor::QNetworkConnectionMonitor() 174 : QObject(*new QNetworkConnectionMonitorPrivate) 175{ 176} 177 178QNetworkConnectionMonitor::QNetworkConnectionMonitor(const QHostAddress &local, const QHostAddress &remote) 179 : QObject(*new QNetworkConnectionMonitorPrivate) 180{ 181 setTargets(local, remote); 182} 183 184QNetworkConnectionMonitor::~QNetworkConnectionMonitor() 185{ 186 Q_D(QNetworkConnectionMonitor); 187 188 stopMonitoring(); 189 d->reset(); 190} 191 192bool QNetworkConnectionMonitor::setTargets(const QHostAddress &local, const QHostAddress &remote) 193{ 194 Q_D(QNetworkConnectionMonitor); 195 196 if (isMonitoring()) { 197 qCWarning(lcNetMon, "Monitor is already active, call stopMonitoring() first"); 198 return false; 199 } 200 201 if (local.isNull()) { 202 qCWarning(lcNetMon, "Invalid (null) local address, cannot create a reachability target"); 203 return false; 204 } 205 206 // Clear the old target if needed: 207 d->reset(); 208 209 qt_sockaddr client = qt_hostaddress_to_sockaddr(local); 210 if (remote.isNull()) { 211 // That's a special case our QNetworkStatusMonitor is using (AnyIpv4/6 address to check an overall status). 212 d->probe = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, reinterpret_cast<sockaddr *>(&client)); 213 } else { 214 qt_sockaddr target = qt_hostaddress_to_sockaddr(remote); 215 d->probe = SCNetworkReachabilityCreateWithAddressPair(kCFAllocatorDefault, 216 reinterpret_cast<sockaddr *>(&client), 217 reinterpret_cast<sockaddr *>(&target)); 218 } 219 220 if (d->probe) { 221 // Let's read the initial state so that callback coming later can 222 // see a difference. Ignore errors though. 223 SCNetworkReachabilityGetFlags(d->probe, &d->state); 224 }else { 225 qCWarning(lcNetMon, "Failed to create network reachability probe"); 226 return false; 227 } 228 229 return true; 230} 231 232bool QNetworkConnectionMonitor::startMonitoring() 233{ 234 Q_D(QNetworkConnectionMonitor); 235 236 if (isMonitoring()) { 237 qCWarning(lcNetMon, "Monitor is already active, call stopMonitoring() first"); 238 return false; 239 } 240 241 if (!d->probe) { 242 qCWarning(lcNetMon, "Can not start monitoring, set targets first"); 243 return false; 244 } 245 246 auto queue = qt_reachability_queue(); 247 if (!queue) { 248 qWarning(lcNetMon, "Failed to create a dispatch queue to schedule a probe on"); 249 return false; 250 } 251 252 SCNetworkReachabilityContext context = {}; 253 context.info = d; 254 if (!SCNetworkReachabilitySetCallback(d->probe, QNetworkConnectionMonitorPrivate::probeCallback, &context)) { 255 qWarning(lcNetMon, "Failed to set a reachability callback"); 256 return false; 257 } 258 259 260 if (!SCNetworkReachabilitySetDispatchQueue(d->probe, queue)) { 261 qWarning(lcNetMon, "Failed to schedule a reachability callback on a queue"); 262 return false; 263 } 264 265 return d->scheduled = true; 266} 267 268bool QNetworkConnectionMonitor::isMonitoring() const 269{ 270 Q_D(const QNetworkConnectionMonitor); 271 272 return d->scheduled; 273} 274 275void QNetworkConnectionMonitor::stopMonitoring() 276{ 277 Q_D(QNetworkConnectionMonitor); 278 279 if (d->scheduled) { 280 Q_ASSERT(d->probe); 281 SCNetworkReachabilitySetDispatchQueue(d->probe, nullptr); 282 SCNetworkReachabilitySetCallback(d->probe, nullptr, nullptr); 283 d->scheduled = false; 284 } 285} 286 287bool QNetworkConnectionMonitor::isReachable() 288{ 289 Q_D(QNetworkConnectionMonitor); 290 291 if (isMonitoring()) { 292 qCWarning(lcNetMon, "Calling isReachable() is unsafe after the monitoring started"); 293 return false; 294 } 295 296 if (!d->probe) { 297 qCWarning(lcNetMon, "Reachability is unknown, set the target first"); 298 return false; 299 } 300 301 return d->isReachable(); 302} 303 304class QNetworkStatusMonitorPrivate : public QObjectPrivate 305{ 306public: 307 QNetworkConnectionMonitor ipv4Probe; 308 bool isOnlineIpv4 = false; 309 QNetworkConnectionMonitor ipv6Probe; 310 bool isOnlineIpv6 = false; 311}; 312 313QNetworkStatusMonitor::QNetworkStatusMonitor(QObject *parent) 314 : QObject(*new QNetworkStatusMonitorPrivate, parent) 315{ 316 Q_D(QNetworkStatusMonitor); 317 318 if (d->ipv4Probe.setTargets(QHostAddress::AnyIPv4, {})) { 319 // We manage to create SCNetworkReachabilityRef for IPv4, let's 320 // read the last known state then! 321 d->isOnlineIpv4 = d->ipv4Probe.isReachable(); 322 } 323 324 if (d->ipv6Probe.setTargets(QHostAddress::AnyIPv6, {})) { 325 // We manage to create SCNetworkReachability ref for IPv6, let's 326 // read the last known state then! 327 d->isOnlineIpv6 = d->ipv6Probe.isReachable(); 328 } 329 330 331 connect(&d->ipv4Probe, &QNetworkConnectionMonitor::reachabilityChanged, this, 332 &QNetworkStatusMonitor::reachabilityChanged, Qt::QueuedConnection); 333 connect(&d->ipv6Probe, &QNetworkConnectionMonitor::reachabilityChanged, this, 334 &QNetworkStatusMonitor::reachabilityChanged, Qt::QueuedConnection); 335} 336 337QNetworkStatusMonitor::~QNetworkStatusMonitor() 338{ 339 Q_D(QNetworkStatusMonitor); 340 341 d->ipv4Probe.disconnect(); 342 d->ipv4Probe.stopMonitoring(); 343 d->ipv6Probe.disconnect(); 344 d->ipv6Probe.stopMonitoring(); 345} 346 347bool QNetworkStatusMonitor::start() 348{ 349 Q_D(QNetworkStatusMonitor); 350 351 if (isMonitoring()) { 352 qCWarning(lcNetMon, "Network status monitor is already active"); 353 return true; 354 } 355 356 d->ipv4Probe.startMonitoring(); 357 d->ipv6Probe.startMonitoring(); 358 359 return isMonitoring(); 360} 361 362void QNetworkStatusMonitor::stop() 363{ 364 Q_D(QNetworkStatusMonitor); 365 366 if (d->ipv4Probe.isMonitoring()) 367 d->ipv4Probe.stopMonitoring(); 368 if (d->ipv6Probe.isMonitoring()) 369 d->ipv6Probe.stopMonitoring(); 370} 371 372bool QNetworkStatusMonitor::isMonitoring() const 373{ 374 Q_D(const QNetworkStatusMonitor); 375 376 return d->ipv4Probe.isMonitoring() || d->ipv6Probe.isMonitoring(); 377} 378 379bool QNetworkStatusMonitor::isNetworkAccessible() 380{ 381 // This function is to be executed on the thread that created 382 // and uses 'this'. 383 Q_D(QNetworkStatusMonitor); 384 385 return d->isOnlineIpv4 || d->isOnlineIpv6; 386} 387 388bool QNetworkStatusMonitor::event(QEvent *event) 389{ 390 return QObject::event(event); 391} 392 393bool QNetworkStatusMonitor::isEnabled() 394{ 395 return true; 396} 397 398void QNetworkStatusMonitor::reachabilityChanged(bool online) 399{ 400 // This function is executed on the thread that created/uses 'this', 401 // not on the reachability queue. 402 Q_D(QNetworkStatusMonitor); 403 404 auto probe = qobject_cast<QNetworkConnectionMonitor *>(sender()); 405 if (!probe) 406 return; 407 408 const bool isIpv4 = probe == &d->ipv4Probe; 409 bool &probeOnline = isIpv4 ? d->isOnlineIpv4 : d->isOnlineIpv6; 410 bool otherOnline = isIpv4 ? d->isOnlineIpv6 : d->isOnlineIpv4; 411 412 if (probeOnline == online) { 413 // We knew this already? 414 return; 415 } 416 417 probeOnline = online; 418 if (!otherOnline) { 419 // We either just lost or got a network access. 420 emit onlineStateChanged(probeOnline); 421 } 422} 423 424QT_END_NAMESPACE 425