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