From cba4c454543ab83797b44946b132323687a198dc Mon Sep 17 00:00:00 2001 From: Alex Blasche Date: Fri, 19 May 2017 16:44:06 +0200 Subject: Obtain random bt address flag via Bluetooth Mgmt API DBus does not expose this information. The Bluetooth Management API advertises the information but requires CAP_NET_ADMIN. The Management class monitors DEVICE FOUND events and records the random bit flag of each new device. QBluetoothDeviceDiscoveryAgent can subsequently add this information to the internal data set. Ultimately this makes QLowEnergyController::setRemoteAddressType obsolete. This function was only ever required on Linux. Task-number: QTBUG-46776 Change-Id: If10df86d332dfc7cc98b7c783eb28487c3fa9045 Reviewed-by: Kai Koehne --- src/bluetooth/bluez/bluetoothmanagement.cpp | 314 +++++++++++++++++++++ src/bluetooth/bluez/bluetoothmanagement_p.h | 98 +++++++ src/bluetooth/bluez/bluez.pri | 6 +- src/bluetooth/bluez/bluez_data_p.h | 7 +- .../qbluetoothdevicediscoveryagent_bluez.cpp | 3 + src/bluetooth/qbluetoothsocket_p.h | 4 +- src/bluetooth/qlowenergycontroller.cpp | 15 +- src/bluetooth/qlowenergycontroller_bluez.cpp | 19 +- 8 files changed, 455 insertions(+), 11 deletions(-) create mode 100644 src/bluetooth/bluez/bluetoothmanagement.cpp create mode 100644 src/bluetooth/bluez/bluetoothmanagement_p.h diff --git a/src/bluetooth/bluez/bluetoothmanagement.cpp b/src/bluetooth/bluez/bluetoothmanagement.cpp new file mode 100644 index 00000000..9df74b34 --- /dev/null +++ b/src/bluetooth/bluez/bluetoothmanagement.cpp @@ -0,0 +1,314 @@ +/**************************************************************************** +** +** 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 "../qbluetoothsocket_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 diff --git a/src/bluetooth/bluez/bluetoothmanagement_p.h b/src/bluetooth/bluez/bluetoothmanagement_p.h new file mode 100644 index 00000000..954f6e03 --- /dev/null +++ b/src/bluetooth/bluez/bluetoothmanagement_p.h @@ -0,0 +1,98 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef BLUETOOTHMANAGEMENT_P_H +#define BLUETOOTHMANAGEMENT_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include + +#include + +#ifndef QPRIVATELINEARBUFFER_BUFFERSIZE +#define QPRIVATELINEARBUFFER_BUFFERSIZE Q_INT64_C(16384) +#endif +#include "../qprivatelinearbuffer_p.h" + +QT_BEGIN_NAMESPACE + +class QSocketNotifier; + +class BluetoothManagement : public QObject +{ + Q_OBJECT + +public: + explicit BluetoothManagement(QObject *parent = nullptr); + static BluetoothManagement *instance(); + + bool isAddressRandom(const QBluetoothAddress &address) const; + bool isMonitoringEnabled() const; + +private slots: + void _q_readNotifier(); + void processRandomAddressFlagInformation(const QBluetoothAddress &address); + void cleanupOldAddressFlags(); + +private: + void readyRead(); + + int fd = -1; + QSocketNotifier* notifier; + QPrivateLinearBuffer buffer; + QHash privateFlagAddresses; + mutable QMutex accessLock; +}; + + +QT_END_NAMESPACE + +#endif // BLUETOOTHMANAGEMENT_P_H diff --git a/src/bluetooth/bluez/bluez.pri b/src/bluetooth/bluez/bluez.pri index 3ff2e9d8..b99f2712 100644 --- a/src/bluetooth/bluez/bluez.pri +++ b/src/bluetooth/bluez/bluez.pri @@ -19,7 +19,8 @@ HEADERS += bluez/manager_p.h \ bluez/obex_transfer1_bluez5_p.h \ bluez/bluez_data_p.h \ bluez/hcimanager_p.h \ - bluez/remotedevicemanager_p.h + bluez/remotedevicemanager_p.h \ + bluez/bluetoothmanagement_p.h SOURCES += bluez/manager.cpp \ bluez/adapter.cpp \ @@ -41,4 +42,5 @@ SOURCES += bluez/manager.cpp \ bluez/obex_objectpush1_bluez5.cpp \ bluez/obex_transfer1_bluez5.cpp \ bluez/hcimanager.cpp \ - bluez/remotedevicemanager.cpp + bluez/remotedevicemanager.cpp \ + bluez/bluetoothmanagement.cpp diff --git a/src/bluetooth/bluez/bluez_data_p.h b/src/bluetooth/bluez/bluez_data_p.h index 25edc661..684cd5b8 100644 --- a/src/bluetooth/bluez/bluez_data_p.h +++ b/src/bluetooth/bluez/bluez_data_p.h @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** 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. @@ -204,7 +204,10 @@ template<> inline void putBtData(quint128 src, void *dst) // HCI related -#define HCI_MAX_DEV 16 +#define HCI_MAX_DEV 16 +#define HCI_DEV_NONE 0xffff + +#define HCI_CHANNEL_CONTROL 0x3 #define HCI_MAX_EVENT_SIZE 260 diff --git a/src/bluetooth/qbluetoothdevicediscoveryagent_bluez.cpp b/src/bluetooth/qbluetoothdevicediscoveryagent_bluez.cpp index 5288eaf8..b913bde4 100644 --- a/src/bluetooth/qbluetoothdevicediscoveryagent_bluez.cpp +++ b/src/bluetooth/qbluetoothdevicediscoveryagent_bluez.cpp @@ -51,6 +51,7 @@ #include "bluez/adapter1_bluez5_p.h" #include "bluez/device1_bluez5_p.h" #include "bluez/properties_p.h" +#include "bluez/bluetoothmanagement_p.h" QT_BEGIN_NAMESPACE @@ -82,6 +83,8 @@ QBluetoothDeviceDiscoveryAgentPrivate::QBluetoothDeviceDiscoveryAgentPrivate( SIGNAL(InterfacesAdded(QDBusObjectPath,InterfaceList)), q, SLOT(_q_InterfacesAdded(QDBusObjectPath,InterfaceList))); + // start private address monitoring + BluetoothManagement::instance(); } else { manager = new OrgBluezManagerInterface(QStringLiteral("org.bluez"), QStringLiteral("/"), QDBusConnection::systemBus(), parent); diff --git a/src/bluetooth/qbluetoothsocket_p.h b/src/bluetooth/qbluetoothsocket_p.h index c1af9471..177f154a 100644 --- a/src/bluetooth/qbluetoothsocket_p.h +++ b/src/bluetooth/qbluetoothsocket_p.h @@ -265,7 +265,7 @@ public slots: #endif // QT_OSX_BLUETOOTH -static inline void convertAddress(quint64 from, quint8 (&to)[6]) +static inline void convertAddress(const quint64 from, quint8 (&to)[6]) { to[0] = (from >> 0) & 0xff; to[1] = (from >> 8) & 0xff; @@ -275,7 +275,7 @@ static inline void convertAddress(quint64 from, quint8 (&to)[6]) to[5] = (from >> 40) & 0xff; } -static inline quint64 convertAddress(quint8 (&from)[6], quint64 *to = 0) +static inline quint64 convertAddress(const quint8 (&from)[6], quint64 *to = 0) { const quint64 result = (quint64(from[0]) << 0) | (quint64(from[1]) << 8) | diff --git a/src/bluetooth/qlowenergycontroller.cpp b/src/bluetooth/qlowenergycontroller.cpp index a3aad282..2337268d 100644 --- a/src/bluetooth/qlowenergycontroller.cpp +++ b/src/bluetooth/qlowenergycontroller.cpp @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** 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. @@ -691,6 +691,19 @@ QLowEnergyController::RemoteAddressType QLowEnergyController::remoteAddressType( /*! Sets the remote address \a type. The type is required to connect to the remote Bluetooth Low Energy device. + + This attribute is only required to be set on Linux/BlueZ systems with older + Linux kernels (v3.3 or lower), or if CAP_NET_ADMIN is not set for the executable. + The default value of the attribute is \l RandomAddress. + + \note All other platforms handle this flag transparently and therefore applications + can ignore it entirely. On Linux, the address type flag is not directly exposed + by BlueZ although some use cases do require this information. The only way to detect + the flag is via the Linux kernel's Bluetooth Management API (kernel + version 3.4+ required). This API requires CAP_NET_ADMIN capabilities though. If the + local QtBluetooth process has this capability set QtBluetooth will use the API. This + assumes that \l QBluetoothDeviceDiscoveryAgent was used prior to calling + \l QLowEnergyController::connectToDevice(). */ void QLowEnergyController::setRemoteAddressType( QLowEnergyController::RemoteAddressType type) diff --git a/src/bluetooth/qlowenergycontroller_bluez.cpp b/src/bluetooth/qlowenergycontroller_bluez.cpp index 9dadab91..583c0e27 100644 --- a/src/bluetooth/qlowenergycontroller_bluez.cpp +++ b/src/bluetooth/qlowenergycontroller_bluez.cpp @@ -46,6 +46,7 @@ #include "bluez/hcimanager_p.h" #include "bluez/remotedevicemanager_p.h" #include "bluez/bluez5_helper_p.h" +#include "bluez/bluetoothmanagement_p.h" #include #include @@ -598,10 +599,20 @@ void QLowEnergyControllerPrivate::establishL2cpClientSocket() this, SLOT(l2cpErrorChanged(QBluetoothSocket::SocketError))); connect(l2cpSocket, SIGNAL(readyRead()), this, SLOT(l2cpReadyRead())); - if (addressType == QLowEnergyController::PublicAddress) - l2cpSocket->d_ptr->lowEnergySocketType = BDADDR_LE_PUBLIC; - else if (addressType == QLowEnergyController::RandomAddress) - l2cpSocket->d_ptr->lowEnergySocketType = BDADDR_LE_RANDOM; + quint32 addressTypeToUse = (addressType == QLowEnergyController::PublicAddress) + ? BDADDR_LE_PUBLIC : BDADDR_LE_RANDOM; + if (BluetoothManagement::instance()->isMonitoringEnabled()) { + // if monitoring is possible and it's private then we force it to the relevant option + if (BluetoothManagement::instance()->isAddressRandom(remoteDevice)) { + addressTypeToUse = BDADDR_LE_RANDOM; + } + } + + qCDebug(QT_BT_BLUEZ) << "addresstypeToUse:" + << (addressTypeToUse == BDADDR_LE_RANDOM + ? QStringLiteral("Random") : QStringLiteral("Public")); + + l2cpSocket->d_ptr->lowEnergySocketType = addressTypeToUse; int sockfd = l2cpSocket->socketDescriptor(); if (sockfd < 0) { -- cgit v1.2.3