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