/**************************************************************************** ** ** Copyright (C) 2017 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtBluetooth module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include #include #include #include "bluetoothmanagement_p.h" #include "bluez_data_p.h" #include "../qbluetoothsocketbase_p.h" #include #include #include #include #include QT_BEGIN_NAMESPACE // Packet data structures for Mgmt API bluez.git/doc/mgmt-api.txt enum class EventCode { DeviceFound = 0x0012, }; struct MgmtHdr { quint16 cmdCode; quint16 controllerIndex; quint16 length; } __attribute__((packed)); struct MgmtEventDeviceFound { bdaddr_t bdaddr; quint8 type; quint8 rssi; quint32 flags; quint16 eirLength; quint8 eirData[0]; } __attribute__((packed)); /* * This class encapsulates access to the Bluetooth Management API as introduced by * Linux kernel 3.4. Some Bluetooth information is not exposed via the usual DBus * API (e.g. the random/public address type info). In those cases we have to fall back * to this mgmt API. * * Note that opening such a Bluetooth mgmt socket requires CAP_NET_ADMIN (root) capability. * * Documentation can be found in bluez-git/doc/mgmt-api.txt */ Q_DECLARE_LOGGING_CATEGORY(QT_BT_BLUEZ) // These structs and defines come straight from linux/capability.h. // To avoid missing definitions we re-define them if not existing. // In addition, we don't want to pull in a libcap2 dependency struct capHdr { quint32 version; int pid; }; struct capData { quint32 effective; quint32 permitted; quint32 inheritable; }; #ifndef _LINUX_CAPABILITY_VERSION_3 #define _LINUX_CAPABILITY_VERSION_3 0x20080522 #endif #ifndef _LINUX_CAPABILITY_U32S_3 #define _LINUX_CAPABILITY_U32S_3 2 #endif #ifndef CAP_NET_ADMIN #define CAP_NET_ADMIN 12 #endif #ifndef CAP_TO_INDEX #define CAP_TO_INDEX(x) ((x) >> 5) /* 1 << 5 == bits in __u32 */ #endif #ifndef CAP_TO_MASK #define CAP_TO_MASK(x) (1 << ((x) & 31)) /* mask for indexed __u32 */ #endif const int msecInADay = 1000*60*60*24; inline uint qHash(const QBluetoothAddress& address) { return qHash(address.toUInt64()); } static int sysCallCapGet(capHdr *header, capData *data) { return syscall(__NR_capget, header, data); } /* * Checks that the current process has the effective CAP_NET_ADMIN permission. */ static bool hasBtMgmtPermission() { // We only care for cap version 3 introduced by kernel 2.6.26 // because the new BlueZ management API only exists since kernel 3.4. struct capHdr header = {}; struct capData data[_LINUX_CAPABILITY_U32S_3] = {{}}; header.version = _LINUX_CAPABILITY_VERSION_3; header.pid = getpid(); if (sysCallCapGet(&header, data) < 0) { qCWarning(QT_BT_BLUEZ, "BluetoothManangement: getCap failed with %s", qPrintable(qt_error_string(errno))); return false; } return (data[CAP_TO_INDEX(CAP_NET_ADMIN)].effective & CAP_TO_MASK(CAP_NET_ADMIN)); } BluetoothManagement::BluetoothManagement(QObject *parent) : QObject(parent) { bool hasPermission = hasBtMgmtPermission(); if (!hasPermission) { qCInfo(QT_BT_BLUEZ, "Missing CAP_NET_ADMIN permission. Cannot determine whether " "a found address is of random or public type."); return; } sockaddr_hci hciAddr; fd = ::socket(AF_BLUETOOTH, SOCK_RAW | SOCK_CLOEXEC | SOCK_NONBLOCK, BTPROTO_HCI); if (fd < 0) { qCWarning(QT_BT_BLUEZ, "Cannot open Bluetooth Management socket: %s", qPrintable(qt_error_string(errno))); return; } memset(&hciAddr, 0, sizeof(hciAddr)); hciAddr.hci_dev = HCI_DEV_NONE; hciAddr.hci_channel = HCI_CHANNEL_CONTROL; hciAddr.hci_family = AF_BLUETOOTH; if (::bind(fd, (struct sockaddr *)(&hciAddr), sizeof(hciAddr)) < 0) { qCWarning(QT_BT_BLUEZ, "Cannot bind Bluetooth Management socket: %s", qPrintable(qt_error_string(errno))); ::close(fd); fd = -1; return; } notifier = new QSocketNotifier(fd, QSocketNotifier::Read, this); connect(notifier, &QSocketNotifier::activated, this, &BluetoothManagement::_q_readNotifier); // ensure cache is regularly cleaned (once every 24h) QTimer* timer = new QTimer(this); timer->setInterval(msecInADay); timer->setTimerType(Qt::VeryCoarseTimer); connect(timer, &QTimer::timeout, this, &BluetoothManagement::cleanupOldAddressFlags); timer->start(); } Q_GLOBAL_STATIC(BluetoothManagement, bluetoothKernelManager) BluetoothManagement *BluetoothManagement::instance() { return bluetoothKernelManager(); } void BluetoothManagement::_q_readNotifier() { char *dst = buffer.reserve(QPRIVATELINEARBUFFER_BUFFERSIZE); int readCount = ::read(fd, dst, QPRIVATELINEARBUFFER_BUFFERSIZE); buffer.chop(QPRIVATELINEARBUFFER_BUFFERSIZE - (readCount < 0 ? 0 : readCount)); if (readCount < 0) { qCWarning(QT_BT_BLUEZ, "Management Control read error %s", qPrintable(qt_error_string(errno))); return; } // do we have at least one complete mgmt header? if ((uint)buffer.size() < sizeof(MgmtHdr)) return; QByteArray data = buffer.readAll(); while (true) { if ((uint)data.size() < sizeof(MgmtHdr)) break; const MgmtHdr *hdr = reinterpret_cast(data.constData()); const int nextPackageSize = qFromLittleEndian(hdr->length) + sizeof(MgmtHdr); const int remainingPackageSize = data.length() - nextPackageSize; if (data.length() < nextPackageSize) break; // not a complete event header -> wait for next notifier switch (static_cast(qFromLittleEndian(hdr->cmdCode))) { case EventCode::DeviceFound: { const MgmtEventDeviceFound *event = reinterpret_cast (data.constData() + sizeof(MgmtHdr)); if (event->type == BDADDR_LE_RANDOM) { const bdaddr_t address = event->bdaddr; quint64 bdaddr; convertAddress(address.b, &bdaddr); const QBluetoothAddress qtAddress(bdaddr); qCDebug(QT_BT_BLUEZ) << "BluetoothManagement: found random device" << qtAddress; processRandomAddressFlagInformation(qtAddress); } break; } default: qCDebug(QT_BT_BLUEZ) << "BluetoothManagement: Ignored event:" << hex << qFromLittleEndian(hdr->cmdCode); break; } if (data.length() > nextPackageSize) data = data.right(remainingPackageSize); else data.clear(); if (data.isEmpty()) break; } if (!data.isEmpty()) buffer.ungetBlock(data.constData(), data.size()); } void BluetoothManagement::processRandomAddressFlagInformation(const QBluetoothAddress &address) { // insert or update QMutexLocker locker(&accessLock); privateFlagAddresses[address] = QDateTime::currentDateTimeUtc(); } /* * Ensure that private address cache is not older than 24h. */ void BluetoothManagement::cleanupOldAddressFlags() { const auto cutOffTime = QDateTime::currentDateTimeUtc().addDays(-1); QMutexLocker locker(&accessLock); auto i = privateFlagAddresses.begin(); while (i != privateFlagAddresses.end()) { if (i.value() < cutOffTime) i = privateFlagAddresses.erase(i); else i++; } } bool BluetoothManagement::isAddressRandom(const QBluetoothAddress &address) const { if (fd == -1 || address.isNull()) return false; QMutexLocker locker(&accessLock); return privateFlagAddresses.contains(address); } bool BluetoothManagement::isMonitoringEnabled() const { return (fd == -1) ? false : true; } QT_END_NAMESPACE