diff --git a/CMakeLists.txt b/CMakeLists.txt index 94c6bed23..372b12ab4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -65,7 +65,7 @@ else() find_package(Qca-qt${QT_MAJOR_VERSION} ${QCA_MIN_VERSION} REQUIRED) set(Qca_LIBRARY qca-qt${QT_MAJOR_VERSION}) - set(KF5_REQUIRED_COMPONENTS I18n ConfigWidgets DBusAddons IconThemes Notifications KIO KCMUtils Service Solid Kirigami2 People WindowSystem GuiAddons) + set(KF5_REQUIRED_COMPONENTS I18n ConfigWidgets DBusAddons IconThemes Notifications KIO KCMUtils Service Solid Kirigami2 People WindowSystem GuiAddons DNSSD) set(KF5_OPTIONAL_COMPONENTS DocTools) set_package_properties(KF5Kirigami2 PROPERTIES diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index 442a6de06..b5de85d6e 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -63,6 +63,7 @@ PRIVATE Qt${QT_MAJOR_VERSION}::DBus KF5::I18n KF5::ConfigCore + KF5::DNSSD ) if(WIN32) diff --git a/core/backends/lan/CMakeLists.txt b/core/backends/lan/CMakeLists.txt index 02bc9d5b6..042581cdd 100644 --- a/core/backends/lan/CMakeLists.txt +++ b/core/backends/lan/CMakeLists.txt @@ -7,5 +7,7 @@ set(backends_kdeconnect_SRCS backends/lan/landevicelink.cpp backends/lan/compositeuploadjob.cpp backends/lan/uploadjob.cpp + backends/lan/mdnsdiscovery.cpp + PARENT_SCOPE ) diff --git a/core/backends/lan/lanlinkprovider.cpp b/core/backends/lan/lanlinkprovider.cpp index 6f71066ef..b5ecd8b1e 100644 --- a/core/backends/lan/lanlinkprovider.cpp +++ b/core/backends/lan/lanlinkprovider.cpp @@ -46,6 +46,7 @@ LanLinkProvider::LanLinkProvider(bool testMode, quint16 udpBroadcastPort, quint1 , m_udpListenPort(udpListenPort) , m_testMode(testMode) , m_combineBroadcastsTimer(this) + , m_mdnsDiscovery(this) { m_combineBroadcastsTimer.setInterval(0); // increase this if waiting a single event-loop iteration is not enough m_combineBroadcastsTimer.setSingleShot(true); @@ -102,12 +103,17 @@ void LanLinkProvider::onStart() } } - onNetworkChange(); + broadcastUdpIdentityPacket(); + m_mdnsDiscovery.startAnnouncing(); + m_mdnsDiscovery.startDiscovering(); + qCDebug(KDECONNECT_CORE) << "LanLinkProvider started"; } void LanLinkProvider::onStop() { + m_mdnsDiscovery.stopAnnouncing(); + m_mdnsDiscovery.stopDiscovering(); m_udpSocket.close(); m_server->close(); qCDebug(KDECONNECT_CORE) << "LanLinkProvider stopped"; @@ -126,15 +132,25 @@ void LanLinkProvider::onNetworkChange() void LanLinkProvider::broadcastToNetwork() { if (!m_server->isListening()) { - // Not started + qWarning() << "TCP server not listening, not broadcasting"; return; } Q_ASSERT(m_tcpPort != 0); - qCDebug(KDECONNECT_CORE()) << "Broadcasting identity packet"; + broadcastUdpIdentityPacket(); + m_mdnsDiscovery.stopDiscovering(); + m_mdnsDiscovery.startDiscovering(); +} - QList destinations = getBroadcastAddresses(); +void LanLinkProvider::broadcastUdpIdentityPacket() +{ + sendUdpIdentityPacket(getBroadcastAddresses()); +} + +void LanLinkProvider::sendUdpIdentityPacket(const QList &destinations) +{ + qCDebug(KDECONNECT_CORE()) << "Broadcasting identity packet"; NetworkPacket np = KdeConnectConfig::instance().deviceInfo().toIdentityPacket(); np.set(QStringLiteral("tcpPort"), m_tcpPort); @@ -176,14 +192,14 @@ void LanLinkProvider::broadcastToNetwork() if (sourceAddress.protocol() == QAbstractSocket::IPv4Protocol && sourceAddress != QHostAddress::LocalHost) { qCDebug(KDECONNECT_CORE()) << "Broadcasting as" << sourceAddress; sendSocket.bind(sourceAddress); - sendBroadcasts(sendSocket, np, destinations); + sendUdpPacket(sendSocket, np, destinations); sendSocket.close(); } } } } #else - sendBroadcasts(m_udpSocket, np, destinations); + sendUdpPacket(m_udpSocket, np, destinations); #endif } @@ -210,7 +226,7 @@ QList LanLinkProvider::getBroadcastAddresses() return destinations; } -void LanLinkProvider::sendBroadcasts(QUdpSocket &socket, const NetworkPacket &np, const QList &addresses) +void LanLinkProvider::sendUdpPacket(QUdpSocket &socket, const NetworkPacket &np, const QList &addresses) { const QByteArray payload = np.serialize(); diff --git a/core/backends/lan/lanlinkprovider.h b/core/backends/lan/lanlinkprovider.h index c8e684e1d..24f012dc9 100644 --- a/core/backends/lan/lanlinkprovider.h +++ b/core/backends/lan/lanlinkprovider.h @@ -17,6 +17,7 @@ #include "backends/linkprovider.h" #include "kdeconnectcore_export.h" #include "landevicelink.h" +#include "mdnsdiscovery.h" #include "server.h" class KDECONNECTCORE_EXPORT LanLinkProvider : public LinkProvider @@ -37,6 +38,8 @@ public: return QStringLiteral("LanLinkProvider"); } + void sendUdpIdentityPacket(const QList &addresses); + static void configureSslSocket(QSslSocket *socket, const QString &deviceId, bool isDeviceTrusted); static void configureSocket(QSslSocket *socket); @@ -67,7 +70,8 @@ private: void onNetworkConfigurationChanged(const QNetworkConfiguration &config); void addLink(QSslSocket *socket, const DeviceInfo &deviceInfo); QList getBroadcastAddresses(); - void sendBroadcasts(QUdpSocket &socket, const NetworkPacket &np, const QList &addresses); + void sendUdpPacket(QUdpSocket &socket, const NetworkPacket &np, const QList &addresses); + void broadcastUdpIdentityPacket(); Server *m_server; QUdpSocket m_udpSocket; @@ -86,6 +90,8 @@ private: QNetworkConfiguration m_lastConfig; const bool m_testMode; QTimer m_combineBroadcastsTimer; + + MdnsDiscovery m_mdnsDiscovery; }; #endif diff --git a/core/backends/lan/mdnsdiscovery.cpp b/core/backends/lan/mdnsdiscovery.cpp new file mode 100644 index 000000000..9232aa2bb --- /dev/null +++ b/core/backends/lan/mdnsdiscovery.cpp @@ -0,0 +1,120 @@ +/** + * SPDX-FileCopyrightText: 2023 Albert Vaca + * + * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL + */ + +#include "mdnsdiscovery.h" + +#include "core_debug.h" +#include "kdeconnectconfig.h" +#include "lanlinkprovider.h" + +#include +#include +#include + +const QString kServiceName = QStringLiteral("_kdeconnect._udp"); + +MdnsDiscovery::MdnsDiscovery(LanLinkProvider *lanLinkProvider) + : lanLinkProvider(lanLinkProvider) +{ + switch (KDNSSD::ServiceBrowser::isAvailable()) { + case KDNSSD::ServiceBrowser::Stopped: + qWarning() << "mDNS or Avahi daemons are not running, mDNS discovery not available"; + break; + case KDNSSD::ServiceBrowser::Working: + qCDebug(KDECONNECT_CORE) << "mDNS discovery is available"; + break; + case KDNSSD::ServiceBrowser::Unsupported: + qWarning() << "mDNS discovery not available (library built without DNS-SD support)"; + break; + } +} + +MdnsDiscovery::~MdnsDiscovery() +{ + stopAnnouncing(); + stopDiscovering(); +} + +void MdnsDiscovery::startAnnouncing() +{ + if (m_publisher != nullptr) { + qCDebug(KDECONNECT_CORE) << "MDNS already announcing"; + return; + } + qCDebug(KDECONNECT_CORE) << "MDNS start announcing"; + + KdeConnectConfig &config = KdeConnectConfig::instance(); + + m_publisher = new KDNSSD::PublicService(config.deviceId(), kServiceName, LanLinkProvider::UDP_PORT, QStringLiteral("local")); + m_publisher->setParent(this); + + // We can't fit the device certificate in this field, so this is not enough info to create a Device and won't be used. + QMap data; + data[QStringLiteral("id")] = config.deviceId().toUtf8(); + data[QStringLiteral("name")] = config.name().toUtf8(); + data[QStringLiteral("type")] = config.deviceType().toString().toUtf8(); + data[QStringLiteral("protocol")] = QString::number(NetworkPacket::s_protocolVersion).toUtf8(); + m_publisher->setTextData(data); + + connect(m_publisher, &KDNSSD::PublicService::published, [](bool successful) { + if (successful) { + qCDebug(KDECONNECT_CORE) << "MDNS published successfully"; + } else { + qWarning() << "MDNS failed to publish"; + } + }); + + m_publisher->publishAsync(); +} + +void MdnsDiscovery::stopAnnouncing() +{ + if (m_publisher != nullptr) { + qCDebug(KDECONNECT_CORE) << "MDNS stop announcing"; + delete m_publisher; + m_publisher = nullptr; + } +} + +void MdnsDiscovery::startDiscovering() +{ + if (m_serviceBrowser != nullptr) { + qCDebug(KDECONNECT_CORE) << "MDNS already discovering"; + return; + } + qCDebug(KDECONNECT_CORE) << "MDNS start discovering"; + + m_serviceBrowser = new KDNSSD::ServiceBrowser(kServiceName, true); + + connect(m_serviceBrowser, &KDNSSD::ServiceBrowser::serviceAdded, [this](KDNSSD::RemoteService::Ptr service) { + if (KdeConnectConfig::instance().deviceId() == service->serviceName()) { + qCDebug(KDECONNECT_CORE) << "Discovered myself, ignoring"; + return; + } + qCDebug(KDECONNECT_CORE) << "Discovered " << service->serviceName() << " at " << service->hostName(); + QHostAddress address(service->hostName()); + lanLinkProvider->sendUdpIdentityPacket(QList{address}); + }); + + connect(m_serviceBrowser, &KDNSSD::ServiceBrowser::serviceRemoved, [](KDNSSD::RemoteService::Ptr service) { + qCDebug(KDECONNECT_CORE) << "Lost " << service->serviceName(); + }); + + connect(m_serviceBrowser, &KDNSSD::ServiceBrowser::finished, []() { + qCDebug(KDECONNECT_CORE) << "Finished discovery"; + }); + + m_serviceBrowser->startBrowse(); +} + +void MdnsDiscovery::stopDiscovering() +{ + if (m_serviceBrowser != nullptr) { + qCDebug(KDECONNECT_CORE) << "MDNS stop discovering"; + delete m_serviceBrowser; + m_serviceBrowser = nullptr; + } +} diff --git a/core/backends/lan/mdnsdiscovery.h b/core/backends/lan/mdnsdiscovery.h new file mode 100644 index 000000000..2fb31df8b --- /dev/null +++ b/core/backends/lan/mdnsdiscovery.h @@ -0,0 +1,41 @@ +/** + * SPDX-FileCopyrightText: 2023 Albert Vaca Cintora + * + * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL + */ + +#ifndef KDECONNECT_MDNS_DISCOVERY_H +#define KDECONNECT_MDNS_DISCOVERY_H + +#include + +#include "kdeconnectcore_export.h" + +class LanLinkProvider; +namespace KDNSSD +{ +class PublicService; +class ServiceBrowser; +}; + +class KDECONNECTCORE_EXPORT MdnsDiscovery : public QObject +{ + Q_OBJECT + +public: + explicit MdnsDiscovery(LanLinkProvider *parent); + ~MdnsDiscovery(); + + void startDiscovering(); + void stopDiscovering(); + + void stopAnnouncing(); + void startAnnouncing(); + +private: + LanLinkProvider *lanLinkProvider = nullptr; + KDNSSD::PublicService *m_publisher = nullptr; + KDNSSD::ServiceBrowser *m_serviceBrowser = nullptr; +}; + +#endif // KDECONNECT_SERVER_H