1/****************************************************************************
2**
3** Copyright (C) 2017 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtBluetooth 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 <QtCore/qloggingcategory.h>
41#include <QtCore/qsocketnotifier.h>
42#include <QtCore/qtimer.h>
43
44#include "bluetoothmanagement_p.h"
45#include "bluez_data_p.h"
46#include "../qbluetoothsocketbase_p.h"
47
48#include <unistd.h>
49#include <sys/prctl.h>
50#include <sys/syscall.h>
51#include <sys/types.h>
52#include <linux/capability.h>
53
54
55QT_BEGIN_NAMESPACE
56
57// Packet data structures for Mgmt API bluez.git/doc/mgmt-api.txt
58
59enum class EventCode {
60 DeviceFound = 0x0012,
61};
62
63struct MgmtHdr {
64 quint16 cmdCode;
65 quint16 controllerIndex;
66 quint16 length;
67} __attribute__((packed));
68
69struct MgmtEventDeviceFound {
70 bdaddr_t bdaddr;
71 quint8 type;
72 quint8 rssi;
73 quint32 flags;
74 quint16 eirLength;
75 quint8 eirData[0];
76} __attribute__((packed));
77
78
79/*
80 * This class encapsulates access to the Bluetooth Management API as introduced by
81 * Linux kernel 3.4. Some Bluetooth information is not exposed via the usual DBus
82 * API (e.g. the random/public address type info). In those cases we have to fall back
83 * to this mgmt API.
84 *
85 * Note that opening such a Bluetooth mgmt socket requires CAP_NET_ADMIN (root) capability.
86 *
87 * Documentation can be found in bluez-git/doc/mgmt-api.txt
88 */
89
90Q_DECLARE_LOGGING_CATEGORY(QT_BT_BLUEZ)
91
92// These structs and defines come straight from linux/capability.h.
93// To avoid missing definitions we re-define them if not existing.
94// In addition, we don't want to pull in a libcap2 dependency
95struct capHdr {
96 quint32 version;
97 int pid;
98};
99
100struct capData {
101 quint32 effective;
102 quint32 permitted;
103 quint32 inheritable;
104};
105
106#ifndef _LINUX_CAPABILITY_VERSION_3
107#define _LINUX_CAPABILITY_VERSION_3 0x20080522
108#endif
109
110#ifndef _LINUX_CAPABILITY_U32S_3
111#define _LINUX_CAPABILITY_U32S_3 2
112#endif
113
114#ifndef CAP_NET_ADMIN
115#define CAP_NET_ADMIN 12
116#endif
117
118#ifndef CAP_TO_INDEX
119#define CAP_TO_INDEX(x) ((x) >> 5) /* 1 << 5 == bits in __u32 */
120#endif
121
122#ifndef CAP_TO_MASK
123#define CAP_TO_MASK(x) (1 << ((x) & 31)) /* mask for indexed __u32 */
124#endif
125
126const int msecInADay = 1000*60*60*24;
127
128inline uint qHash(const QBluetoothAddress& address)
129{
130 return qHash(address.toUInt64());
131}
132
133static int sysCallCapGet(capHdr *header, capData *data)
134{
135 return syscall(__NR_capget, header, data);
136}
137
138/*
139 * Checks that the current process has the effective CAP_NET_ADMIN permission.
140 */
141static bool hasBtMgmtPermission()
142{
143 // We only care for cap version 3 introduced by kernel 2.6.26
144 // because the new BlueZ management API only exists since kernel 3.4.
145
146 struct capHdr header = {};
147 struct capData data[_LINUX_CAPABILITY_U32S_3] = {{}};
148 header.version = _LINUX_CAPABILITY_VERSION_3;
149 header.pid = getpid();
150
151 if (sysCallCapGet(&header, data) < 0) {
152 qCWarning(QT_BT_BLUEZ, "BluetoothManangement: getCap failed with %s",
153 qPrintable(qt_error_string(errno)));
154 return false;
155 }
156
157 return (data[CAP_TO_INDEX(CAP_NET_ADMIN)].effective & CAP_TO_MASK(CAP_NET_ADMIN));
158}
159
160BluetoothManagement::BluetoothManagement(QObject *parent) : QObject(parent)
161{
162 bool hasPermission = hasBtMgmtPermission();
163 if (!hasPermission) {
164 qCInfo(QT_BT_BLUEZ, "Missing CAP_NET_ADMIN permission. Cannot determine whether "
165 "a found address is of random or public type.");
166 return;
167 }
168
169 sockaddr_hci hciAddr;
170
171 fd = ::socket(AF_BLUETOOTH, SOCK_RAW | SOCK_CLOEXEC | SOCK_NONBLOCK, BTPROTO_HCI);
172 if (fd < 0) {
173 qCWarning(QT_BT_BLUEZ, "Cannot open Bluetooth Management socket: %s",
174 qPrintable(qt_error_string(errno)));
175 return;
176 }
177
178 memset(&hciAddr, 0, sizeof(hciAddr));
179 hciAddr.hci_dev = HCI_DEV_NONE;
180 hciAddr.hci_channel = HCI_CHANNEL_CONTROL;
181 hciAddr.hci_family = AF_BLUETOOTH;
182
183 if (::bind(fd, (struct sockaddr *)(&hciAddr), sizeof(hciAddr)) < 0) {
184 qCWarning(QT_BT_BLUEZ, "Cannot bind Bluetooth Management socket: %s",
185 qPrintable(qt_error_string(errno)));
186 ::close(fd);
187 fd = -1;
188 return;
189 }
190
191 notifier = new QSocketNotifier(fd, QSocketNotifier::Read, this);
192 connect(notifier, &QSocketNotifier::activated, this, &BluetoothManagement::_q_readNotifier);
193
194 // ensure cache is regularly cleaned (once every 24h)
195 QTimer* timer = new QTimer(this);
196 timer->setInterval(msecInADay);
197 timer->setTimerType(Qt::VeryCoarseTimer);
198 connect(timer, &QTimer::timeout, this, &BluetoothManagement::cleanupOldAddressFlags);
199 timer->start();
200}
201
202Q_GLOBAL_STATIC(BluetoothManagement, bluetoothKernelManager)
203
204BluetoothManagement *BluetoothManagement::instance()
205{
206 return bluetoothKernelManager();
207}
208
209void BluetoothManagement::_q_readNotifier()
210{
211 char *dst = buffer.reserve(QPRIVATELINEARBUFFER_BUFFERSIZE);
212 int readCount = ::read(fd, dst, QPRIVATELINEARBUFFER_BUFFERSIZE);
213 buffer.chop(QPRIVATELINEARBUFFER_BUFFERSIZE - (readCount < 0 ? 0 : readCount));
214 if (readCount < 0) {
215 qCWarning(QT_BT_BLUEZ, "Management Control read error %s", qPrintable(qt_error_string(errno)));
216 return;
217 }
218
219 // do we have at least one complete mgmt header?
220 if ((uint)buffer.size() < sizeof(MgmtHdr))
221 return;
222
223 QByteArray data = buffer.readAll();
224
225 while (true) {
226 if ((uint)data.size() < sizeof(MgmtHdr))
227 break;
228
229 const MgmtHdr *hdr = reinterpret_cast<const MgmtHdr*>(data.constData());
230 const int nextPackageSize = qFromLittleEndian(hdr->length) + sizeof(MgmtHdr);
231 const int remainingPackageSize = data.length() - nextPackageSize;
232
233 if (data.length() < nextPackageSize)
234 break; // not a complete event header -> wait for next notifier
235
236 switch (static_cast<EventCode>(qFromLittleEndian(hdr->cmdCode))) {
237 case EventCode::DeviceFound:
238 {
239 const MgmtEventDeviceFound *event = reinterpret_cast<const MgmtEventDeviceFound*>
240 (data.constData() + sizeof(MgmtHdr));
241
242 if (event->type == BDADDR_LE_RANDOM) {
243 const bdaddr_t address = event->bdaddr;
244 quint64 bdaddr;
245
246 convertAddress(address.b, &bdaddr);
247 const QBluetoothAddress qtAddress(bdaddr);
248 qCDebug(QT_BT_BLUEZ) << "BluetoothManagement: found random device"
249 << qtAddress;
250 processRandomAddressFlagInformation(qtAddress);
251 }
252
253 break;
254 }
255 default:
256 qCDebug(QT_BT_BLUEZ) << "BluetoothManagement: Ignored event:"
257 << hex << qFromLittleEndian(hdr->cmdCode);
258 break;
259 }
260
261 if (data.length() > nextPackageSize)
262 data = data.right(remainingPackageSize);
263 else
264 data.clear();
265
266 if (data.isEmpty())
267 break;
268 }
269
270 if (!data.isEmpty())
271 buffer.ungetBlock(data.constData(), data.size());
272}
273
274void BluetoothManagement::processRandomAddressFlagInformation(const QBluetoothAddress &address)
275{
276 // insert or update
277 QMutexLocker locker(&accessLock);
278 privateFlagAddresses[address] = QDateTime::currentDateTimeUtc();
279}
280
281/*
282 * Ensure that private address cache is not older than 24h.
283 */
284void BluetoothManagement::cleanupOldAddressFlags()
285{
286 const auto cutOffTime = QDateTime::currentDateTimeUtc().addDays(-1);
287
288 QMutexLocker locker(&accessLock);
289
290 auto i = privateFlagAddresses.begin();
291 while (i != privateFlagAddresses.end()) {
292 if (i.value() < cutOffTime)
293 i = privateFlagAddresses.erase(i);
294 else
295 i++;
296 }
297}
298
299bool BluetoothManagement::isAddressRandom(const QBluetoothAddress &address) const
300{
301 if (fd == -1 || address.isNull())
302 return false;
303
304 QMutexLocker locker(&accessLock);
305 return privateFlagAddresses.contains(address);
306}
307
308bool BluetoothManagement::isMonitoringEnabled() const
309{
310 return (fd == -1) ? false : true;
311}
312
313
314QT_END_NAMESPACE
315