| 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(n: 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 |  |