summaryrefslogtreecommitdiffstats
path: root/src/bluetooth/bluez/bluetoothmanagement.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/bluetooth/bluez/bluetoothmanagement.cpp')
-rw-r--r--src/bluetooth/bluez/bluetoothmanagement.cpp314
1 files changed, 314 insertions, 0 deletions
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 <QtCore/qloggingcategory.h>
+#include <QtCore/qsocketnotifier.h>
+#include <QtCore/qtimer.h>
+
+#include "bluetoothmanagement_p.h"
+#include "bluez_data_p.h"
+#include "../qbluetoothsocket_p.h"
+
+#include <unistd.h>
+#include <sys/prctl.h>
+#include <sys/syscall.h>
+#include <sys/types.h>
+#include <linux/capability.h>
+
+
+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<const MgmtHdr*>(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<EventCode>(qFromLittleEndian(hdr->cmdCode))) {
+ case EventCode::DeviceFound:
+ {
+ const MgmtEventDeviceFound *event = reinterpret_cast<const MgmtEventDeviceFound*>
+ (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