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

source code of qtconnectivity/src/bluetooth/bluez/bluetoothmanagement.cpp