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 | |
20 | QT_BEGIN_NAMESPACE |
21 | |
22 | // Packet data structures for Mgmt API bluez.git/doc/mgmt-api.txt |
23 | |
24 | struct MgmtHdr { |
25 | quint16 cmdCode; |
26 | quint16 controllerIndex; |
27 | quint16 length; |
28 | } __attribute__((packed)); |
29 | |
30 | struct MgmtEventDeviceFound { |
31 | bdaddr_t bdaddr; |
32 | quint8 type; |
33 | quint8 ; |
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 | |
51 | Q_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 |
56 | struct capHdr { |
57 | quint32 version; |
58 | int pid; |
59 | }; |
60 | |
61 | struct 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 | |
87 | const int msecInADay = 1000*60*60*24; |
88 | |
89 | static int sysCallCapGet(capHdr *, 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 | */ |
97 | static 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 = {}; |
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 | |
116 | BluetoothManagement::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 | |
158 | Q_GLOBAL_STATIC(BluetoothManagement, bluetoothKernelManager) |
159 | |
160 | BluetoothManagement *BluetoothManagement::instance() |
161 | { |
162 | return bluetoothKernelManager(); |
163 | } |
164 | |
165 | void 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 | |
230 | void 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 | */ |
240 | void 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 | |
255 | bool 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 | |
264 | bool BluetoothManagement::isMonitoringEnabled() const |
265 | { |
266 | return (fd == -1) ? false : true; |
267 | } |
268 | |
269 | |
270 | QT_END_NAMESPACE |
271 | |
272 | #include "moc_bluetoothmanagement_p.cpp" |
273 | |