From 8ba476250ee26e86c4b5b2c010a692c5b3231387 Mon Sep 17 00:00:00 2001 From: Julia Nelz <121945980+I-asked@users.noreply.github.com> Date: Tue, 30 Apr 2024 09:12:56 +0200 Subject: [PATCH 1/6] Modify tap2ble for the libslirp-based implementation - Use Connman for managing the TAP interface - Rely on kernel packet fragmentation instead of a custom header - Respect the negotiated MTU when choosing the TAP MTU --- asteroid-tap2ble.service | 8 ++- src/ble-dbus.cpp | 14 +++- src/ble-dbus.h | 10 +-- src/ble.cpp | 49 ++++--------- src/ble.h | 7 +- src/main.cpp | 1 + src/tap.cpp | 151 ++++++++++++++++++++++++++++++++++----- src/tap.h | 5 ++ 8 files changed, 181 insertions(+), 64 deletions(-) diff --git a/asteroid-tap2ble.service b/asteroid-tap2ble.service index 777b3a5..22fe8f3 100644 --- a/asteroid-tap2ble.service +++ b/asteroid-tap2ble.service @@ -3,12 +3,16 @@ Description=Starts the AsteroidOS IP over BLE connectivity daemon Requires=dbus.socket After=bluetooth.service ConditionUser=root - + [Service] Type=simple ExecStart=/usr/bin/asteroid-tap2ble Restart=always - +User=ceres +Group=ceres +AmbientCapabilities=CAP_NET_ADMIN +NoNewPrivileges=true + [Install] WantedBy=default.target diff --git a/src/ble-dbus.cpp b/src/ble-dbus.cpp index 381e669..43cdc1a 100644 --- a/src/ble-dbus.cpp +++ b/src/ble-dbus.cpp @@ -18,10 +18,22 @@ #include "ble-dbus.h" // Called when a companion writes to the RX characteristic -void RXChrc::WriteValue(QByteArray data, QVariantMap) { +void RXChrc::WriteValue(QByteArray data, QVariantMap properties) { + if (properties.contains("mtu")) { + emit mtuChanged(properties["mtu"].toUInt()); + } else + qWarning() << "mtu not in WriteValue"; emit receivedFromCompanion(data); } +QByteArray TXChrc::ReadValue(QVariantMap properties) { + if (properties.contains("mtu")) { + emit mtuChanged(properties["mtu"].toUInt()); + } else + qWarning() << "mtu not in ReadValue"; + return m_value; +} + // Forwards information to the companion by notifications on the TX characteristic void TXChrc::sendToCompanion(QByteArray content) { m_value = content; diff --git a/src/ble-dbus.h b/src/ble-dbus.h index a16988c..a905504 100644 --- a/src/ble-dbus.h +++ b/src/ble-dbus.h @@ -34,9 +34,9 @@ #define TX_PATH "/org/asteroidos/tap2ble/service/tx" #define RX_PATH "/org/asteroidos/tap2ble/service/rx" -#define SERVICE_UUID "00001071-0000-0000-0000-00A57E401D05" -#define RX_UUID "00001001-0000-0000-0000-00A57E401D05" -#define TX_UUID "00001002-0000-0000-0000-00A57E401D05" +#define SERVICE_UUID "0000A071-0000-0000-0000-00A57E401D05" +#define RX_UUID "0000A001-0000-0000-0000-00A57E401D05" +#define TX_UUID "0000A002-0000-0000-0000-00A57E401D05" // Writable characteristic for companion to watch communication class RXChrc : public QObject @@ -62,6 +62,7 @@ public slots: signals: void receivedFromCompanion(const QByteArray &); + void mtuChanged(int); }; // Notifiable characteristic for watch to companion communication @@ -86,12 +87,13 @@ public slots: void WriteValue(QByteArray value, QVariantMap options) {} void StartNotify() {} void StopNotify() {} - QByteArray ReadValue(QVariantMap) { return m_value; } + QByteArray ReadValue(QVariantMap); void sendToCompanion(QByteArray content); signals: void valueChanged(); + void mtuChanged(int); private: void emitPropertiesChanged() { diff --git a/src/ble.cpp b/src/ble.cpp index 8961c32..2486a07 100644 --- a/src/ble.cpp +++ b/src/ble.cpp @@ -35,6 +35,8 @@ #define GATT_DESC_IFACE "org.bluez.GattDescriptor1" BLE::BLE(QObject *parent) : QObject(parent), mBus(QDBusConnection::systemBus()) { + mCurrentMtu = -1; + qDBusRegisterMetaType(); qDBusRegisterMetaType(); @@ -59,6 +61,9 @@ BLE::BLE(QObject *parent) : QObject(parent), mBus(QDBusConnection::systemBus()) connect(&mRX, SIGNAL(receivedFromCompanion(QByteArray)), this, SLOT(onReceivedFromCompanion(QByteArray))); + connect(&mRX, SIGNAL(mtuChanged(int)), this, SLOT(onMtuChanged(int))); + connect(&mTX, SIGNAL(mtuChanged(int)), this, SLOT(onMtuChanged(int))); + QDBusInterface remoteOm(BLUEZ_SERVICE_NAME, "/", DBUS_OM_IFACE, mBus); if (remoteOm.isValid()) bluezServiceRegistered(BLUEZ_SERVICE_NAME); @@ -163,47 +168,17 @@ void BLE::onConnectedChanged() { } } -// TODO: This should be determined dynamically based on the BLE MTU -#define CHUNK_SIZE 240 - void BLE::sendToCompanion(const QByteArray &content) { - uint8_t seqNum = 0; - int currentIndex = 0; - - while (currentIndex < content.size()) { - // The header is one bit of "there are more chunks" and 7 bits of - // sequence number to detect dropped messages - uint8_t header = (seqNum & 0x7F) | - ((currentIndex + CHUNK_SIZE-1) < content.size() ? 0x80 : 0); - QByteArray headerBA((char*)&header, sizeof(header)); - - mTX.sendToCompanion(headerBA + content.mid(currentIndex, CHUNK_SIZE - 1)); - - currentIndex += CHUNK_SIZE - 1; - seqNum++; - } + mTX.sendToCompanion(content); } void BLE::onReceivedFromCompanion(const QByteArray &content) { - static int8_t lastSeqNum = -1; - - uint8_t header = content.at(0); - bool hasMore = !!(header & 0x80); - uint8_t seqNum = header & 0x7F; - - if (seqNum != lastSeqNum + 1) { - mAccumulatedRecv = QByteArray(); - lastSeqNum = -1; - return; - } - - lastSeqNum = seqNum; - mAccumulatedRecv.append(content.mid(1, -1)); + emit receivedFromCompanion(content); +} - if (!hasMore) { - emit receivedFromCompanion(mAccumulatedRecv); - mAccumulatedRecv = QByteArray(); - lastSeqNum = -1; +void BLE::onMtuChanged(int mtu) { + if (mCurrentMtu != mtu) { + mCurrentMtu = mtu; + emit mtuChanged(mtu); } } - diff --git a/src/ble.h b/src/ble.h index 14c0601..881159c 100644 --- a/src/ble.h +++ b/src/ble.h @@ -47,17 +47,18 @@ class BLE : public QObject QString mAdapter; bool mConnected; + int mCurrentMtu; + void updateAdapter(); void setAdapter(QString adatper); void setConnected(bool connected); - QByteArray mAccumulatedRecv; - signals: void connectedChanged(); void adapterChanged(); void receivedFromCompanion(const QByteArray &data); + void mtuChanged(int mtu); public slots: void bluezServiceRegistered(const QString &name); @@ -68,6 +69,8 @@ public slots: void onConnectedChanged(); void onAdapterChanged(); + void onMtuChanged(int); + private slots: void onReceivedFromCompanion(const QByteArray &data); }; diff --git a/src/main.cpp b/src/main.cpp index 25c8f9d..2b65a7f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -25,6 +25,7 @@ int main(int argc, char *argv[]) { TAP tap; BLE ble; + QObject::connect(&ble, &BLE::mtuChanged, &tap, &TAP::mtuChanged); QObject::connect(&tap, &TAP::dataAvailable, &ble, &BLE::sendToCompanion); QObject::connect(&ble, &BLE::receivedFromCompanion, &tap, &TAP::send); diff --git a/src/tap.cpp b/src/tap.cpp index 23e2caf..a3a445d 100644 --- a/src/tap.cpp +++ b/src/tap.cpp @@ -18,14 +18,21 @@ #include "tap.h" #include +#include +#include +#include +#include #include #include #include #include +#include #include #include +#define MY_ETH_TAP_HARD_FRAME_SIZE (14) + TAP::TAP(QObject *parent) : QObject(parent) { // Create the interface mFd = open("/dev/net/tun", O_RDWR); @@ -41,27 +48,135 @@ TAP::TAP(QObject *parent) : QObject(parent) { close(mFd); exit(1); } + if (ioctl(mFd, TUNSETOFFLOAD, 0U) < 0) { + qCritical() << "Failed to set the TAP offload"; + } + + mIfaceName = ifr.ifr_name; + qDebug() << "TAP interface created:" << mIfaceName; - QString ifaceName = ifr.ifr_name; - qDebug() << "TAP interface created:" << ifaceName; + iffReset(244); - // Bring the interface up - if (QProcess::execute("ip", {"link", "set", "dev", ifaceName, "up"}) != 0) { - qCritical() << "Failed to bring up TAP interface"; + char discard[1500]; + auto drained = read(mFd, discard, 1500); + qDebug() << "Drained" << drained << "bytes"; + + // Poll the interface file descriptor + mNotifier = new QSocketNotifier(mFd, QSocketNotifier::Read, this); + QObject::connect(mNotifier, &QSocketNotifier::activated, this, &TAP::fdActivated); +} + +void TAP::iffReset(int mtu) { + struct ifreq ifr = {}; + auto sock = socket(PF_INET, SOCK_DGRAM, 0); + if (sock < 0) { + qCritical() << "Failed to create a socket"; exit(1); } - qDebug() << "TAP interface" << ifaceName << "is up"; - - // Configure routing tables - if (QProcess::execute("ip", {"route", "add", "default", "dev", ifaceName}) != 0) { - qCritical() << "Failed to configure routing table"; + strncpy(ifr.ifr_name, mIfaceName.toLocal8Bit().data(), sizeof(ifr.ifr_name)); + if (ioctl(sock, SIOCGIFFLAGS, &ifr) < 0) { + qCritical() << "Failed to get IFFLAGS"; + exit(1); + } + if (ifr.ifr_flags & IFF_UP) { + ifr.ifr_flags &= ~IFF_UP; + if (ioctl(sock, SIOCSIFFLAGS, &ifr) < 0) { + qCritical() << "Failed to set IFFLAGS"; + exit(1); + } + } + if (mtu < 0) { + qDebug() << "Interface down"; + close(sock); + return; + } + ifr.ifr_flags |= IFF_UP; + if (ioctl(sock, SIOCSIFFLAGS, &ifr) < 0) { + qCritical() << "Failed to set IFFLAGS"; exit(1); } - qDebug() << "Routing table configured to send all traffic to" << ifaceName; - // Poll the interface file descriptor - mNotifier = new QSocketNotifier(mFd, QSocketNotifier::Read, this); - QObject::connect(mNotifier, &QSocketNotifier::activated, this, &TAP::fdActivated); + ifr.ifr_mtu = mtu - MY_ETH_TAP_HARD_FRAME_SIZE; + if (ioctl(sock, SIOCSIFMTU, &ifr) < 0) { + qCritical() << "Failed to set the MTU"; + exit(1); + } + + close(sock); + + QDBusConnection bus = QDBusConnection::systemBus(); + QDBusInterface manager("net.connman", "/", "net.connman.Manager", bus); + QDBusMessage result = manager.call("GetServices"); + + const QDBusArgument argument = result.arguments().at(0).value(); + QMap reply; + argument >> reply; + + for (auto it = reply.keyValueBegin(); it != reply.keyValueEnd(); ++it) { + const auto &key = it->first; + const auto &value = it->second; + + if (value.contains("Ethernet")) { + //QString name = value["Ethernet"].toMap()["Name"].toString(); + QDBusArgument ethernetArg = value["Ethernet"].value(); + QVariantMap ethernetMap; + ethernetArg >> ethernetMap; + + const auto name = ethernetMap["Interface"].toString(); + + if (!strcmp(mIfaceName.toLocal8Bit().data(), name.toLocal8Bit().data())) { + qDebug() << "Will reset the IPv4 configuration"; + + QDBusInterface service("net.connman", key.path(), "net.connman.Service", bus); + + { + QDBusArgument propertiesArg = value["IPv4.Configuration"].value(); + QVariantMap propertiesMap; + propertiesArg >> propertiesMap; + + propertiesMap["Method"] = "manual"; + propertiesMap["Address"] = "10.0.2.3"; + propertiesMap["Netmask"] = "255.255.255.0"; + propertiesMap["Gateway"] = "10.0.2.2"; + + QVariantList args; + args << "IPv4.Configuration" << QVariant::fromValue(QDBusVariant(propertiesMap)); + service.callWithArgumentList(QDBus::AutoDetect, "SetProperty", args); + } + + { + QStringList propertiesMap; + + propertiesMap << "10.0.2.2"; + // TODO:XXX: CHANGE THIS!!! + propertiesMap << "8.8.4.4"; + propertiesMap << "8.8.8.8"; + + QVariantList args; + args << "Nameservers.Configuration" << QVariant::fromValue(QDBusVariant(propertiesMap)); + service.callWithArgumentList(QDBus::AutoDetect, "SetProperty", args); + } + } + } + } +} + +void TAP::mtuChanged(int mtu) { + struct ifreq ifr = {}; + auto sock = socket(PF_INET, SOCK_DGRAM, 0); + if (sock < 0) { + qCritical() << "Failed to create a socket"; + exit(1); + } + strncpy(ifr.ifr_name, mIfaceName.toLocal8Bit().data(), sizeof(ifr.ifr_name)); + ifr.ifr_mtu = mtu - MY_ETH_TAP_HARD_FRAME_SIZE; + if (ioctl(sock, SIOCSIFMTU, &ifr) < 0) { + qCritical() << "Failed to set the MTU"; + exit(1); + } + close(sock); + + qDebug() << "MTU reset to" << mtu; } // Called every time the watch kernel outputs ethernet frames to the network @@ -71,8 +186,8 @@ void TAP::fdActivated() { if (bytesRead > 0) { QByteArray data(buffer, bytesRead); - qDebug() << "Received" << bytesRead << "bytes from TAP interface"; - qDebug() << "Data:" << data.toHex(); + // qDebug() << "Received" << bytesRead << "bytes from TAP interface"; + // qDebug() << "Data:" << data.toHex(); emit dataAvailable(data); } else @@ -84,8 +199,8 @@ void TAP::send(const QByteArray &data) { qint64 bytesWritten = write(mFd, data.constData(), data.size()); if (bytesWritten > 0) { - qDebug() << "Sent" << bytesWritten << "bytes to the TAP interface"; - qDebug() << "Data:" << data.toHex(); + // qDebug() << "Sent" << bytesWritten << "bytes to the TAP interface"; + // qDebug() << "Data:" << data.toHex(); } else qCritical() << "Failed to write to TAP interface"; } diff --git a/src/tap.h b/src/tap.h index e00df7d..59550cd 100644 --- a/src/tap.h +++ b/src/tap.h @@ -27,6 +27,10 @@ class TAP : public QObject public: explicit TAP(QObject *parent = 0); void send(const QByteArray &data); + void mtuChanged(int mtu); + +private: + void iffReset(int mtu); signals: void dataAvailable(const QByteArray &data); @@ -35,6 +39,7 @@ private slots: void fdActivated(); private: + QString mIfaceName; QSocketNotifier *mNotifier; int mFd; }; From c0ad1510549759a157cf2d9370800d4396b76ca2 Mon Sep 17 00:00:00 2001 From: Julia Nelz <121945980+I-asked@users.noreply.github.com> Date: Tue, 30 Apr 2024 09:42:24 +0200 Subject: [PATCH 2/6] Update documentation to reflet the libslirp changes --- README.md | 7 ------- 1 file changed, 7 deletions(-) diff --git a/README.md b/README.md index 18f8c44..402f085 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,3 @@ This daemon creates a TAP interface and exposes a BLE service with a RX and a TX characteristic. These map to read/write operations on that TAP interface. - -The protocol takes into account fragmentation due to the BLE MTU so each BLE -message is prepended with a one-byte header that contains a sequence number (to -drop missed messages) and a bit to specify the end of a message. - -This effectively exposes IP connectivity from the watch to companion apps where -this TAP traffic can be injected as RAW sockets or fed to daemons like passt. From c168eac28ef7f561f63dbaf75a8210b2cc242fa0 Mon Sep 17 00:00:00 2001 From: Julia Nelz <121945980+I-asked@users.noreply.github.com> Date: Tue, 30 Apr 2024 09:44:00 +0200 Subject: [PATCH 3/6] Clean up CMakeLists.txt --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8599aaa..54f41a3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,6 +16,6 @@ set(MAIN_SOURCE_FILES src/ble.cpp) add_executable(asteroid-tap2ble ${MAIN_SOURCE_FILES}) -target_link_libraries(asteroid-tap2ble resolv Qt5::Core Qt5::DBus) +target_link_libraries(asteroid-tap2ble Qt5::Core Qt5::DBus) install(TARGETS asteroid-tap2ble DESTINATION bin) From c48a6f45967a71179064419df8d0849a249b8067 Mon Sep 17 00:00:00 2001 From: Julia Nelz <121945980+I-asked@users.noreply.github.com> Date: Thu, 2 May 2024 13:02:01 +0200 Subject: [PATCH 4/6] Use correct MTU --- src/ble.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ble.cpp b/src/ble.cpp index 2486a07..b220682 100644 --- a/src/ble.cpp +++ b/src/ble.cpp @@ -179,6 +179,6 @@ void BLE::onReceivedFromCompanion(const QByteArray &content) { void BLE::onMtuChanged(int mtu) { if (mCurrentMtu != mtu) { mCurrentMtu = mtu; - emit mtuChanged(mtu); + emit mtuChanged(mtu - 3); } } From 8dad96bae4d03a9b7223a01cf3faa67e50830351 Mon Sep 17 00:00:00 2001 From: Julia Nelz <121945980+I-asked@users.noreply.github.com> Date: Thu, 2 May 2024 13:02:15 +0200 Subject: [PATCH 5/6] Make sure connman is started before tap2ble --- asteroid-tap2ble.service | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/asteroid-tap2ble.service b/asteroid-tap2ble.service index 22fe8f3..7a241fd 100644 --- a/asteroid-tap2ble.service +++ b/asteroid-tap2ble.service @@ -1,7 +1,7 @@ [Unit] Description=Starts the AsteroidOS IP over BLE connectivity daemon Requires=dbus.socket -After=bluetooth.service +After=connman.service ConditionUser=root [Service] @@ -10,8 +10,6 @@ ExecStart=/usr/bin/asteroid-tap2ble Restart=always User=ceres Group=ceres -AmbientCapabilities=CAP_NET_ADMIN -NoNewPrivileges=true [Install] WantedBy=default.target From 7a45626a9fbad862eae0a50dd56f9901e57e72c6 Mon Sep 17 00:00:00 2001 From: Julia Nelz <121945980+I-asked@users.noreply.github.com> Date: Thu, 2 May 2024 13:02:58 +0200 Subject: [PATCH 6/6] Update documentation --- README.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 402f085..a255218 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,16 @@ -# asteroid-tap2ble +asteroid-tap2ble +================ This daemon creates a TAP interface and exposes a BLE service with a RX and a TX characteristic. These map to read/write operations on that TAP interface. + +D-Bus forwarding +---------------- + +To expose D-Bus to the companion: + +1. Add a line containing `ListenStream=55556` right above the other + `ListenStream=…` in `/usr/lib/systemd/user/dbus.socket` +2. Enable anonymous authentication in `/usr/share/dbus-1/session.conf` + by adding `` and `ANONYMOUS` + to the ``