Compare commits
15 commits
master
...
work/mdns-
Author | SHA1 | Date | |
---|---|---|---|
|
f007be0dc8 | ||
|
55de49f197 | ||
|
6765f16976 | ||
|
d76e7e0eeb | ||
|
d0777724f5 | ||
|
8e7c461634 | ||
|
b8bee7828e | ||
|
8a05229ac6 | ||
|
3d392b8c49 | ||
|
611923c24a | ||
|
9b6c179e02 | ||
|
cd2bff716e | ||
|
aabc8397b0 | ||
|
4f02bedb11 | ||
|
ce67459d95 |
8 changed files with 2483 additions and 38 deletions
|
@ -7,5 +7,8 @@ set(backends_kdeconnect_SRCS
|
||||||
backends/lan/landevicelink.cpp
|
backends/lan/landevicelink.cpp
|
||||||
backends/lan/compositeuploadjob.cpp
|
backends/lan/compositeuploadjob.cpp
|
||||||
backends/lan/uploadjob.cpp
|
backends/lan/uploadjob.cpp
|
||||||
|
backends/lan/mdnsdiscovery.cpp
|
||||||
|
backends/lan/mdns_wrapper.cpp
|
||||||
|
|
||||||
PARENT_SCOPE
|
PARENT_SCOPE
|
||||||
)
|
)
|
||||||
|
|
|
@ -45,6 +45,7 @@ LanLinkProvider::LanLinkProvider(bool testMode, quint16 udpBroadcastPort, quint1
|
||||||
, m_udpListenPort(udpListenPort)
|
, m_udpListenPort(udpListenPort)
|
||||||
, m_testMode(testMode)
|
, m_testMode(testMode)
|
||||||
, m_combineBroadcastsTimer(this)
|
, m_combineBroadcastsTimer(this)
|
||||||
|
, m_mdnsDiscovery(this)
|
||||||
{
|
{
|
||||||
m_combineBroadcastsTimer.setInterval(0); // increase this if waiting a single event-loop iteration is not enough
|
m_combineBroadcastsTimer.setInterval(0); // increase this if waiting a single event-loop iteration is not enough
|
||||||
m_combineBroadcastsTimer.setSingleShot(true);
|
m_combineBroadcastsTimer.setSingleShot(true);
|
||||||
|
@ -71,6 +72,7 @@ void LanLinkProvider::onNetworkConfigurationChanged(const QNetworkConfiguration
|
||||||
if (m_lastConfig != config && config.state() == QNetworkConfiguration::Active) {
|
if (m_lastConfig != config && config.state() == QNetworkConfiguration::Active) {
|
||||||
m_lastConfig = config;
|
m_lastConfig = config;
|
||||||
onNetworkChange();
|
onNetworkChange();
|
||||||
|
m_mdnsDiscovery.onNetworkChange();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,12 +103,17 @@ void LanLinkProvider::onStart()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onNetworkChange();
|
broadcastUdpIdentityPacket();
|
||||||
|
m_mdnsDiscovery.startAnnouncing();
|
||||||
|
m_mdnsDiscovery.startDiscovering();
|
||||||
|
|
||||||
qCDebug(KDECONNECT_CORE) << "LanLinkProvider started";
|
qCDebug(KDECONNECT_CORE) << "LanLinkProvider started";
|
||||||
}
|
}
|
||||||
|
|
||||||
void LanLinkProvider::onStop()
|
void LanLinkProvider::onStop()
|
||||||
{
|
{
|
||||||
|
m_mdnsDiscovery.stopAnnouncing();
|
||||||
|
m_mdnsDiscovery.stopDiscovering();
|
||||||
m_udpSocket.close();
|
m_udpSocket.close();
|
||||||
m_server->close();
|
m_server->close();
|
||||||
qCDebug(KDECONNECT_CORE) << "LanLinkProvider stopped";
|
qCDebug(KDECONNECT_CORE) << "LanLinkProvider stopped";
|
||||||
|
@ -125,44 +132,26 @@ void LanLinkProvider::onNetworkChange()
|
||||||
void LanLinkProvider::broadcastToNetwork()
|
void LanLinkProvider::broadcastToNetwork()
|
||||||
{
|
{
|
||||||
if (!m_server->isListening()) {
|
if (!m_server->isListening()) {
|
||||||
// Not started
|
qWarning() << "TCP server not listening, not broadcasting";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Q_ASSERT(m_tcpPort != 0);
|
Q_ASSERT(m_tcpPort != 0);
|
||||||
|
|
||||||
|
broadcastUdpIdentityPacket();
|
||||||
|
m_mdnsDiscovery.stopDiscovering();
|
||||||
|
m_mdnsDiscovery.startDiscovering();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LanLinkProvider::broadcastUdpIdentityPacket()
|
||||||
|
{
|
||||||
|
if (qEnvironmentVariableIsSet("KDECONNECT_DISABLE_UDP_BROADCAST")) {
|
||||||
|
qWarning() << "Not broadcasting UDP because KDECONNECT_DISABLE_UDP_BROADCAST is set";
|
||||||
|
return;
|
||||||
|
}
|
||||||
qCDebug(KDECONNECT_CORE()) << "Broadcasting identity packet";
|
qCDebug(KDECONNECT_CORE()) << "Broadcasting identity packet";
|
||||||
|
|
||||||
QList<QHostAddress> destinations = getBroadcastAddresses();
|
QList<QHostAddress> addresses = getBroadcastAddresses();
|
||||||
|
|
||||||
NetworkPacket np = KdeConnectConfig::instance().deviceInfo().toIdentityPacket();
|
|
||||||
np.set(QStringLiteral("tcpPort"), m_tcpPort);
|
|
||||||
#if defined(Q_OS_MAC) || defined(Q_OS_FREEBSD)
|
|
||||||
// On macOS and FreeBSD, the too large UDP packet (larger than MTU) causes
|
|
||||||
// incomplete transmission.
|
|
||||||
// We remove the capacitilities to reduce the discovery packet to the min
|
|
||||||
// MTU of the interfaces with broadcast feature.
|
|
||||||
int mtu = 1500;
|
|
||||||
for (const QNetworkInterface &iface : QNetworkInterface::allInterfaces()) {
|
|
||||||
if ((iface.flags() & QNetworkInterface::IsUp) && (iface.flags() & QNetworkInterface::IsRunning) && (iface.flags() & QNetworkInterface::CanBroadcast)) {
|
|
||||||
int ifaceMtu = iface.maximumTransmissionUnit();
|
|
||||||
if (ifaceMtu < mtu && ifaceMtu > 0) {
|
|
||||||
mtu = ifaceMtu;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
QByteArray payload = np.serialize();
|
|
||||||
if (payload.length() > mtu) {
|
|
||||||
// First try to drop the less important outgoing capabilities
|
|
||||||
np.set(QStringLiteral("outgoingCapabilities"), QStringList());
|
|
||||||
payload = np.serialize();
|
|
||||||
}
|
|
||||||
if (payload.length() > mtu) {
|
|
||||||
// If still too large, drop the incoming capabilities
|
|
||||||
np.set(QStringLiteral("incomingCapabilities"), QStringList());
|
|
||||||
payload = np.serialize();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if defined(Q_OS_WIN) || defined(Q_OS_FREEBSD)
|
#if defined(Q_OS_WIN) || defined(Q_OS_FREEBSD)
|
||||||
// On Windows and FreeBSD we need to broadcast from every local IP address to reach all networks
|
// On Windows and FreeBSD we need to broadcast from every local IP address to reach all networks
|
||||||
|
@ -175,17 +164,18 @@ void LanLinkProvider::broadcastToNetwork()
|
||||||
if (sourceAddress.protocol() == QAbstractSocket::IPv4Protocol && sourceAddress != QHostAddress::LocalHost) {
|
if (sourceAddress.protocol() == QAbstractSocket::IPv4Protocol && sourceAddress != QHostAddress::LocalHost) {
|
||||||
qCDebug(KDECONNECT_CORE()) << "Broadcasting as" << sourceAddress;
|
qCDebug(KDECONNECT_CORE()) << "Broadcasting as" << sourceAddress;
|
||||||
sendSocket.bind(sourceAddress);
|
sendSocket.bind(sourceAddress);
|
||||||
sendBroadcasts(sendSocket, np, destinations);
|
sendUdpIdentityPacket(sendSocket, addresses);
|
||||||
sendSocket.close();
|
sendSocket.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
sendBroadcasts(m_udpSocket, np, destinations);
|
sendUdpIdentityPacket(addresses);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
QList<QHostAddress> LanLinkProvider::getBroadcastAddresses()
|
QList<QHostAddress> LanLinkProvider::getBroadcastAddresses()
|
||||||
{
|
{
|
||||||
const QStringList customDevices = KdeConnectConfig::instance().customDevices();
|
const QStringList customDevices = KdeConnectConfig::instance().customDevices();
|
||||||
|
@ -209,12 +199,31 @@ QList<QHostAddress> LanLinkProvider::getBroadcastAddresses()
|
||||||
return destinations;
|
return destinations;
|
||||||
}
|
}
|
||||||
|
|
||||||
void LanLinkProvider::sendBroadcasts(QUdpSocket &socket, const NetworkPacket &np, const QList<QHostAddress> &addresses)
|
void LanLinkProvider::sendUdpIdentityPacket(const QList<QHostAddress> &addresses)
|
||||||
{
|
{
|
||||||
const QByteArray payload = np.serialize();
|
sendUdpIdentityPacket(m_udpSocket, addresses);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LanLinkProvider::sendUdpIdentityPacket(QUdpSocket &socket, const QList<QHostAddress> &addresses)
|
||||||
|
{
|
||||||
|
DeviceInfo myDeviceInfo = KdeConnectConfig::instance().deviceInfo();
|
||||||
|
NetworkPacket identityPacket = myDeviceInfo.toIdentityPacket();
|
||||||
|
identityPacket.set(QStringLiteral("tcpPort"), m_tcpPort);
|
||||||
|
const QByteArray payload = identityPacket.serialize();
|
||||||
|
|
||||||
for (auto &address : addresses) {
|
for (auto &address : addresses) {
|
||||||
socket.writeDatagram(payload, address, m_udpBroadcastPort);
|
qint64 bytes = socket.writeDatagram(payload, address, m_udpBroadcastPort);
|
||||||
|
if (bytes == -1 && socket.error() == QAbstractSocket::DatagramTooLargeError) {
|
||||||
|
// On macOS and FreeBSD, UDP broadcasts larger than MTU get dropped. See:
|
||||||
|
// https://opensource.apple.com/source/xnu/xnu-3789.1.32/bsd/netinet/ip_output.c.auto.html#:~:text=/*%20don%27t%20allow%20broadcast%20messages%20to%20be%20fragmented%20*/
|
||||||
|
// We remove the capabilities to reduce the size of the packet.
|
||||||
|
// This should only happen for broadcasts, so UDP packets sent from MDNS discoveries should still work.
|
||||||
|
qWarning() << "Identity packet to" << address << "got rejected because it was too large. Retrying without including the capabilities";
|
||||||
|
identityPacket.set(QStringLiteral("outgoingCapabilities"), QStringList());
|
||||||
|
identityPacket.set(QStringLiteral("incomingCapabilities"), QStringList());
|
||||||
|
const QByteArray smallPayload = identityPacket.serialize();
|
||||||
|
socket.writeDatagram(smallPayload, address, m_udpBroadcastPort);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
#include "backends/linkprovider.h"
|
#include "backends/linkprovider.h"
|
||||||
#include "kdeconnectcore_export.h"
|
#include "kdeconnectcore_export.h"
|
||||||
#include "landevicelink.h"
|
#include "landevicelink.h"
|
||||||
|
#include "mdnsdiscovery.h"
|
||||||
#include "server.h"
|
#include "server.h"
|
||||||
|
|
||||||
class KDECONNECTCORE_EXPORT LanLinkProvider : public LinkProvider
|
class KDECONNECTCORE_EXPORT LanLinkProvider : public LinkProvider
|
||||||
|
@ -37,6 +38,8 @@ public:
|
||||||
return QStringLiteral("LanLinkProvider");
|
return QStringLiteral("LanLinkProvider");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void sendUdpIdentityPacket(const QList<QHostAddress> &addresses);
|
||||||
|
|
||||||
static void configureSslSocket(QSslSocket *socket, const QString &deviceId, bool isDeviceTrusted);
|
static void configureSslSocket(QSslSocket *socket, const QString &deviceId, bool isDeviceTrusted);
|
||||||
static void configureSocket(QSslSocket *socket);
|
static void configureSocket(QSslSocket *socket);
|
||||||
|
|
||||||
|
@ -67,7 +70,8 @@ private:
|
||||||
void onNetworkConfigurationChanged(const QNetworkConfiguration &config);
|
void onNetworkConfigurationChanged(const QNetworkConfiguration &config);
|
||||||
void addLink(QSslSocket *socket, const DeviceInfo &deviceInfo);
|
void addLink(QSslSocket *socket, const DeviceInfo &deviceInfo);
|
||||||
QList<QHostAddress> getBroadcastAddresses();
|
QList<QHostAddress> getBroadcastAddresses();
|
||||||
void sendBroadcasts(QUdpSocket &socket, const NetworkPacket &np, const QList<QHostAddress> &addresses);
|
void sendUdpIdentityPacket(QUdpSocket &socket, const QList<QHostAddress> &addresses);
|
||||||
|
void broadcastUdpIdentityPacket();
|
||||||
|
|
||||||
Server *m_server;
|
Server *m_server;
|
||||||
QUdpSocket m_udpSocket;
|
QUdpSocket m_udpSocket;
|
||||||
|
@ -86,6 +90,8 @@ private:
|
||||||
QNetworkConfiguration m_lastConfig;
|
QNetworkConfiguration m_lastConfig;
|
||||||
const bool m_testMode;
|
const bool m_testMode;
|
||||||
QTimer m_combineBroadcastsTimer;
|
QTimer m_combineBroadcastsTimer;
|
||||||
|
|
||||||
|
MdnsDiscovery m_mdnsDiscovery;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
1618
core/backends/lan/mdns.h
Normal file
1618
core/backends/lan/mdns.h
Normal file
File diff suppressed because it is too large
Load diff
604
core/backends/lan/mdns_wrapper.cpp
Normal file
604
core/backends/lan/mdns_wrapper.cpp
Normal file
|
@ -0,0 +1,604 @@
|
||||||
|
/**
|
||||||
|
* SPDX-FileCopyrightText: 2023 Albert Vaca Cintora <albertvaka@gmail.com>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "mdns_wrapper.h"
|
||||||
|
|
||||||
|
#include "core_debug.h"
|
||||||
|
|
||||||
|
#include "mdns.h"
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
#include <QHostInfo>
|
||||||
|
#include <QNetworkInterface>
|
||||||
|
#include <QSocketNotifier>
|
||||||
|
|
||||||
|
namespace MdnsWrapper
|
||||||
|
{
|
||||||
|
|
||||||
|
const char *recordTypeToStr(int rtype)
|
||||||
|
{
|
||||||
|
switch (rtype) {
|
||||||
|
case MDNS_RECORDTYPE_PTR: return "PTR";
|
||||||
|
case MDNS_RECORDTYPE_SRV: return "SRV";
|
||||||
|
case MDNS_RECORDTYPE_TXT: return "TXT";
|
||||||
|
case MDNS_RECORDTYPE_A: return "A";
|
||||||
|
case MDNS_RECORDTYPE_AAAA: return "AAAA";
|
||||||
|
case MDNS_RECORDTYPE_ANY: return "ANY";
|
||||||
|
default: return "UNKNOWN";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *entryTypeToStr(int entry)
|
||||||
|
{
|
||||||
|
switch (entry) {
|
||||||
|
case MDNS_ENTRYTYPE_QUESTION: return "QUESTION";
|
||||||
|
case MDNS_ENTRYTYPE_ANSWER: return "ANSWER";
|
||||||
|
case MDNS_ENTRYTYPE_AUTHORITY: return "AUTHORITY";
|
||||||
|
case MDNS_ENTRYTYPE_ADDITIONAL: return "ADDITIONAL";
|
||||||
|
default: return "UNKNOWN";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static sockaddr_in qHostAddresstoSockaddr(QHostAddress hostAddress)
|
||||||
|
{
|
||||||
|
Q_ASSERT(hostAddress.protocol() == QAbstractSocket::IPv4Protocol);
|
||||||
|
sockaddr_in socketAddress;
|
||||||
|
memset(&socketAddress, 0, sizeof(socketAddress));
|
||||||
|
socketAddress.sin_family = AF_INET;
|
||||||
|
socketAddress.sin_addr.s_addr = htonl(hostAddress.toIPv4Address());
|
||||||
|
return socketAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
static sockaddr_in6 qHostAddresstoSockaddr6(QHostAddress hostAddress)
|
||||||
|
{
|
||||||
|
Q_ASSERT(hostAddress.protocol() == QAbstractSocket::IPv6Protocol);
|
||||||
|
sockaddr_in6 socketAddress;
|
||||||
|
memset(&socketAddress, 0, sizeof(socketAddress));
|
||||||
|
socketAddress.sin6_family = AF_INET6;
|
||||||
|
Q_IPV6ADDR ipv6Address = hostAddress.toIPv6Address();
|
||||||
|
for (int i = 0; i < 16; ++i) {
|
||||||
|
socketAddress.sin6_addr.s6_addr[i] = ipv6Address[i];
|
||||||
|
}
|
||||||
|
return socketAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Callback that handles responses to a query
|
||||||
|
static int query_callback(int sock, const struct sockaddr* from, size_t addrlen, mdns_entry_type_t entry_type,
|
||||||
|
uint16_t query_id, uint16_t record_type, uint16_t rclass, uint32_t ttl, const void* data,
|
||||||
|
size_t size, size_t name_offset, size_t name_length, size_t record_offset,
|
||||||
|
size_t record_length, void* user_data) {
|
||||||
|
Q_UNUSED(sock);
|
||||||
|
Q_UNUSED(addrlen);
|
||||||
|
Q_UNUSED(query_id);
|
||||||
|
Q_UNUSED(entry_type);
|
||||||
|
Q_UNUSED(rclass);
|
||||||
|
Q_UNUSED(ttl);
|
||||||
|
Q_UNUSED(name_offset);
|
||||||
|
Q_UNUSED(name_length);
|
||||||
|
|
||||||
|
//qCDebug(KDECONNECT_CORE) << "Received DNS record of type" << recordTypeToStr(record_type) << "from socket" << sock << "with type" << entryTypeToStr(entry_type);
|
||||||
|
|
||||||
|
Discoverer::MdnsService *discoveredService = (Discoverer::MdnsService *)user_data;
|
||||||
|
|
||||||
|
switch (record_type) {
|
||||||
|
case MDNS_RECORDTYPE_PTR: {
|
||||||
|
// We don't use mdns_record_parse_ptr() because we want to extract just the service name instead of the full "<service-name>.<_service-type>._tcp.local." string
|
||||||
|
mdns_string_pair_t serviceNamePos = mdns_get_next_substring(data, size, record_offset);
|
||||||
|
discoveredService->name = QString::fromLatin1((char *)data + serviceNamePos.offset, serviceNamePos.length);
|
||||||
|
//static char serviceNameBuffer[256];
|
||||||
|
//mdns_string_t serviceName = mdns_record_parse_ptr(data, size, record_offset, record_length, serviceNameBuffer, sizeof(serviceNameBuffer));
|
||||||
|
//discoveredService->name = QString::fromLatin1(serviceName.str, serviceName.length);
|
||||||
|
if (discoveredService->address == QHostAddress::Null) {
|
||||||
|
discoveredService->address = QHostAddress(from); // In case we don't receive a A record, use from as address
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
case MDNS_RECORDTYPE_SRV: {
|
||||||
|
static char nameBuffer[256];
|
||||||
|
mdns_record_srv_t record = mdns_record_parse_srv(data, size, record_offset, record_length, nameBuffer, sizeof(nameBuffer));
|
||||||
|
// We can use the IP to connect so we don't need to store the "<hostname>.local." address.
|
||||||
|
//discoveredService->qualifiedHostname = QString::fromLatin1(record.name.str, record.name.length);
|
||||||
|
discoveredService->port = record.port;
|
||||||
|
} break;
|
||||||
|
case MDNS_RECORDTYPE_A: {
|
||||||
|
sockaddr_in addr;
|
||||||
|
mdns_record_parse_a(data, size, record_offset, record_length, &addr);
|
||||||
|
discoveredService->address = QHostAddress(ntohl(addr.sin_addr.s_addr));
|
||||||
|
} break;
|
||||||
|
case MDNS_RECORDTYPE_AAAA:
|
||||||
|
// Ignore IPv6 for now
|
||||||
|
//sockaddr_in6 addr6;
|
||||||
|
//mdns_record_parse_aaaa(data, size, record_offset, record_length, &addr6);
|
||||||
|
break;
|
||||||
|
case MDNS_RECORDTYPE_TXT: {
|
||||||
|
mdns_record_txt_t records[24];
|
||||||
|
size_t parsed = mdns_record_parse_txt(data, size, record_offset, record_length, records, sizeof(records) / sizeof(mdns_record_txt_t));
|
||||||
|
for (size_t itxt = 0; itxt < parsed; ++itxt) {
|
||||||
|
QString key = QString::fromLatin1(records[itxt].key.str, records[itxt].key.length);
|
||||||
|
QString value = QString::fromLatin1(records[itxt].value.str, records[itxt].value.length);
|
||||||
|
discoveredService->txtRecords[key] = value;
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
default:
|
||||||
|
// Ignore unknown record types
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Discoverer::startDiscovering(const QString &serviceType)
|
||||||
|
{
|
||||||
|
int num_sockets = listenForQueryResponses();
|
||||||
|
if (num_sockets <= 0) {
|
||||||
|
qWarning() << "Failed to open any client sockets";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sendQuery(serviceType);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Discoverer::stopDiscovering()
|
||||||
|
{
|
||||||
|
stopListeningForQueryResponses();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Discoverer::stopListeningForQueryResponses()
|
||||||
|
{
|
||||||
|
qCDebug(KDECONNECT_CORE) << "Closing" << responseSocketNotifiers.size() << "sockets";
|
||||||
|
for (QSocketNotifier *socketNotifier : responseSocketNotifiers) {
|
||||||
|
mdns_socket_close(socketNotifier->socket());
|
||||||
|
delete socketNotifier;
|
||||||
|
}
|
||||||
|
responseSocketNotifiers.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
int Discoverer::listenForQueryResponses()
|
||||||
|
{
|
||||||
|
// Open a socket for each interface
|
||||||
|
QVector<int> sockets;
|
||||||
|
for (const QNetworkInterface &iface : QNetworkInterface::allInterfaces()) {
|
||||||
|
int flags = iface.flags();
|
||||||
|
if (!(flags & QNetworkInterface::IsUp) || !(flags & QNetworkInterface::CanMulticast) || (flags & QNetworkInterface::IsLoopBack)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (const QNetworkAddressEntry &ifaceAddress : iface.addressEntries()) {
|
||||||
|
QHostAddress sourceAddress = ifaceAddress.ip();
|
||||||
|
if (sourceAddress.protocol() == QAbstractSocket::IPv4Protocol && sourceAddress != QHostAddress::LocalHost) {
|
||||||
|
qCDebug(KDECONNECT_CORE) << "Opening socket for address" << sourceAddress;
|
||||||
|
struct sockaddr_in saddr = qHostAddresstoSockaddr(sourceAddress);
|
||||||
|
int socket = mdns_socket_open_ipv4(&saddr);
|
||||||
|
sockets.append(socket);
|
||||||
|
} else if (sourceAddress.protocol() == QAbstractSocket::IPv6Protocol && sourceAddress != QHostAddress::LocalHostIPv6) {
|
||||||
|
qCDebug(KDECONNECT_CORE) << "Opening socket for address6" << sourceAddress;
|
||||||
|
struct sockaddr_in6 saddr = qHostAddresstoSockaddr6(sourceAddress);
|
||||||
|
int socket = mdns_socket_open_ipv6(&saddr);
|
||||||
|
sockets.append(socket);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start listening on all sockets
|
||||||
|
for (int socket : sockets) {
|
||||||
|
QSocketNotifier *socketNotifier = new QSocketNotifier(socket, QSocketNotifier::Read);
|
||||||
|
QObject::connect(socketNotifier, &QSocketNotifier::activated, [this](QSocketDescriptor socket) {
|
||||||
|
MdnsService discoveredService;
|
||||||
|
|
||||||
|
static char buffer[2048];
|
||||||
|
size_t num_records = mdns_query_recv(socket, buffer, sizeof(buffer), query_callback, (void *)&discoveredService, 0);
|
||||||
|
Q_UNUSED(num_records);
|
||||||
|
|
||||||
|
// qCDebug(KDECONNECT_CORE) << "Discovered service" << discoveredService.name << "at" << discoveredService.address << "in" << num_records <<
|
||||||
|
// "records via socket" << socket;
|
||||||
|
|
||||||
|
Q_EMIT serviceFound(discoveredService);
|
||||||
|
});
|
||||||
|
responseSocketNotifiers.append(socketNotifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
qCDebug(KDECONNECT_CORE) << "Opened" << sockets.size() << "sockets to listen for MDNS query responses";
|
||||||
|
|
||||||
|
return sockets.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Discoverer::sendQuery(const QString &serviceType)
|
||||||
|
{
|
||||||
|
qCDebug(KDECONNECT_CORE) << "Sending MDNS query for service" << serviceType;
|
||||||
|
|
||||||
|
mdns_query_t query;
|
||||||
|
QByteArray serviceTypeBytes = serviceType.toLatin1();
|
||||||
|
query.name = serviceTypeBytes.constData();
|
||||||
|
query.length = serviceTypeBytes.length();
|
||||||
|
query.type = MDNS_RECORDTYPE_PTR;
|
||||||
|
|
||||||
|
static char buffer[2048];
|
||||||
|
for (QSocketNotifier *socketNotifier : responseSocketNotifiers) {
|
||||||
|
int socket = socketNotifier->socket();
|
||||||
|
qCDebug(KDECONNECT_CORE) << "Sending mDNS query via socket" << socket;
|
||||||
|
int ret = mdns_multiquery_send(socket, &query, 1, buffer, sizeof(buffer), 0);
|
||||||
|
if (ret < 0) {
|
||||||
|
qWarning() << "Failed to send mDNS query:" << strerror(errno);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const QByteArray dnsSdName = QByteArray("_services._dns-sd._udp.local.");
|
||||||
|
|
||||||
|
static mdns_string_t createMdnsString(const QByteArray &str)
|
||||||
|
{
|
||||||
|
return mdns_string_t{str.constData(), (size_t)str.length()};
|
||||||
|
}
|
||||||
|
|
||||||
|
static QHostAddress findBestAddressMatch(QVector<QHostAddress> hostAddresses, const struct sockaddr *fromAddress)
|
||||||
|
{
|
||||||
|
if (hostAddresses.size() == 1 || fromAddress == nullptr) {
|
||||||
|
return hostAddresses[0];
|
||||||
|
}
|
||||||
|
// FIXME
|
||||||
|
return hostAddresses[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
static mdns_record_t createMdnsRecord(const Announcer::AnnouncedInfo &self,
|
||||||
|
mdns_record_type_t record_type,
|
||||||
|
const struct sockaddr *fromAddress = nullptr, // used to determine IP to set in A and AAAA records
|
||||||
|
QHash<QByteArray, QByteArray>::const_iterator txtIterator = {})
|
||||||
|
{
|
||||||
|
mdns_record_t answer;
|
||||||
|
answer.type = record_type;
|
||||||
|
answer.rclass = 0;
|
||||||
|
answer.ttl = 0;
|
||||||
|
switch (record_type) {
|
||||||
|
case MDNS_RECORDTYPE_PTR: // maps "<_service-type>._tcp.local." to "<service-name>.<_service-type>._tcp.local."
|
||||||
|
answer.name = createMdnsString(self.serviceType);
|
||||||
|
answer.data.ptr.name = createMdnsString(self.serviceInstance);
|
||||||
|
break;
|
||||||
|
case MDNS_RECORDTYPE_SRV: // maps "<service-name>.<_service-type>._tcp.local." to "<hostname>.local." and port
|
||||||
|
answer.name = createMdnsString(self.serviceInstance);
|
||||||
|
answer.data.srv.name = createMdnsString(self.hostname);
|
||||||
|
answer.data.srv.port = self.port;
|
||||||
|
answer.data.srv.priority = 0;
|
||||||
|
answer.data.srv.weight = 0;
|
||||||
|
break;
|
||||||
|
case MDNS_RECORDTYPE_A: // maps "<hostname>.local." to IPv4
|
||||||
|
answer.name = createMdnsString(self.hostname);
|
||||||
|
answer.data.a.addr = qHostAddresstoSockaddr(findBestAddressMatch(self.addressesV4, fromAddress));
|
||||||
|
break;
|
||||||
|
case MDNS_RECORDTYPE_AAAA: // maps "<hostname>.local." to IPv6
|
||||||
|
answer.name = createMdnsString(self.hostname);
|
||||||
|
answer.data.aaaa.addr = qHostAddresstoSockaddr6(findBestAddressMatch(self.addressesV6, fromAddress));
|
||||||
|
break;
|
||||||
|
case MDNS_RECORDTYPE_TXT:
|
||||||
|
answer.name = createMdnsString(self.serviceInstance);
|
||||||
|
answer.type = MDNS_RECORDTYPE_TXT;
|
||||||
|
answer.data.txt.key = createMdnsString(txtIterator.key());
|
||||||
|
answer.data.txt.value = createMdnsString(txtIterator.value());
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Q_ASSERT(false);
|
||||||
|
}
|
||||||
|
return answer;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Callback handling questions incoming on service sockets
|
||||||
|
static int service_callback(int sock, const struct sockaddr* from, size_t addrlen, mdns_entry_type_t entry_type,
|
||||||
|
uint16_t query_id, uint16_t record_type, uint16_t rclass, uint32_t ttl, const void* data,
|
||||||
|
size_t size, size_t name_offset, size_t name_length, size_t record_offset,
|
||||||
|
size_t record_length, void* user_data) {
|
||||||
|
Q_UNUSED(ttl);
|
||||||
|
Q_UNUSED(name_length);
|
||||||
|
Q_UNUSED(record_offset);
|
||||||
|
Q_UNUSED(record_length);
|
||||||
|
static char sendbuffer[2024];
|
||||||
|
|
||||||
|
const Announcer::AnnouncedInfo &self = *((Announcer::AnnouncedInfo *)user_data);
|
||||||
|
|
||||||
|
if (entry_type != MDNS_ENTRYTYPE_QUESTION) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char nameBuffer[256];
|
||||||
|
mdns_string_t nameMdnsString = mdns_string_extract(data, size, &name_offset, nameBuffer, sizeof(nameBuffer));
|
||||||
|
QByteArray name = QByteArray(nameMdnsString.str, nameMdnsString.length);
|
||||||
|
|
||||||
|
if (name == dnsSdName) {
|
||||||
|
qWarning() << "Someone queried all services for" << recordTypeToStr(record_type);
|
||||||
|
if ((record_type == MDNS_RECORDTYPE_PTR) || (record_type == MDNS_RECORDTYPE_ANY)) {
|
||||||
|
// The PTR query was for the DNS-SD domain, send answer with a PTR record for the service name we advertise.
|
||||||
|
|
||||||
|
mdns_record_t answer = createMdnsRecord(self, MDNS_RECORDTYPE_PTR);
|
||||||
|
|
||||||
|
// Send the answer, unicast or multicast depending on flag in query
|
||||||
|
uint16_t unicast = (rclass & MDNS_UNICAST_RESPONSE);
|
||||||
|
|
||||||
|
printf(" --> answer %.*s (%s)\n", MDNS_STRING_FORMAT(answer.data.ptr.name), (unicast ? "unicast" : "multicast"));
|
||||||
|
|
||||||
|
if (unicast) {
|
||||||
|
mdns_query_answer_unicast(sock, from, addrlen, sendbuffer, sizeof(sendbuffer), query_id,
|
||||||
|
(mdns_record_type_t)record_type, nameMdnsString.str, nameMdnsString.length,
|
||||||
|
answer, NULL, 0, NULL, 0);
|
||||||
|
} else {
|
||||||
|
mdns_query_answer_multicast(sock, sendbuffer, sizeof(sendbuffer), answer, 0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (name == self.serviceType) {
|
||||||
|
qWarning() << "Someone queried my service type for" << recordTypeToStr(record_type);
|
||||||
|
if ((record_type == MDNS_RECORDTYPE_PTR) || (record_type == MDNS_RECORDTYPE_ANY)) {
|
||||||
|
// The PTR query was for our service, answer a PTR record reverse mapping the queried service name
|
||||||
|
// to our service instance name and add additional records containing the SRV record mapping the
|
||||||
|
// service instance name to our qualified hostname and port, as well as any IPv4/IPv6 and TXT records
|
||||||
|
|
||||||
|
mdns_record_t answer = createMdnsRecord(self, MDNS_RECORDTYPE_PTR);
|
||||||
|
|
||||||
|
QVector<mdns_record_t> additional;
|
||||||
|
additional.append(createMdnsRecord(self, MDNS_RECORDTYPE_SRV));
|
||||||
|
if (!self.addressesV4.empty()) {
|
||||||
|
additional.append(createMdnsRecord(self, MDNS_RECORDTYPE_A, from));
|
||||||
|
}
|
||||||
|
if (!self.addressesV6.empty()) {
|
||||||
|
additional.append(createMdnsRecord(self, MDNS_RECORDTYPE_AAAA, from));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto txtIterator = self.txtRecords.cbegin(); txtIterator != self.txtRecords.cend(); txtIterator++) {
|
||||||
|
additional.append(createMdnsRecord(self, MDNS_RECORDTYPE_TXT, nullptr, txtIterator));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send the answer, unicast or multicast depending on flag in query
|
||||||
|
uint16_t unicast = (rclass & MDNS_UNICAST_RESPONSE);
|
||||||
|
printf(" --> answer %.*s (%s)\n", MDNS_STRING_FORMAT(answer.data.ptr.name), (unicast ? "unicast" : "multicast"));
|
||||||
|
|
||||||
|
if (unicast) {
|
||||||
|
mdns_query_answer_unicast(sock, from, addrlen, sendbuffer, sizeof(sendbuffer), query_id,
|
||||||
|
(mdns_record_type_t)record_type, nameMdnsString.str, nameMdnsString.length,
|
||||||
|
answer, 0, 0, additional.constData(), additional.length());
|
||||||
|
} else {
|
||||||
|
mdns_query_answer_multicast(sock, sendbuffer, sizeof(sendbuffer), answer, 0, 0,
|
||||||
|
additional.constData(), additional.length());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (name == self.serviceInstance) {
|
||||||
|
qWarning() << "Someone queried my service instance" << recordTypeToStr(record_type);
|
||||||
|
if ((record_type == MDNS_RECORDTYPE_SRV) || (record_type == MDNS_RECORDTYPE_ANY)) {
|
||||||
|
// The SRV query was for our service instance, answer a SRV record mapping the service
|
||||||
|
// instance name to our qualified hostname (typically "<hostname>.local.") and port, as
|
||||||
|
// well as any IPv4/IPv6 address for the hostname as A/AAAA records and TXT records
|
||||||
|
|
||||||
|
mdns_record_t answer = createMdnsRecord(self, MDNS_RECORDTYPE_SRV);
|
||||||
|
|
||||||
|
QVector<mdns_record_t> additional;
|
||||||
|
if (!self.addressesV4.empty()) {
|
||||||
|
additional.append(createMdnsRecord(self, MDNS_RECORDTYPE_A, from));
|
||||||
|
}
|
||||||
|
if (!self.addressesV6.empty()) {
|
||||||
|
additional.append(createMdnsRecord(self, MDNS_RECORDTYPE_AAAA, from));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto txtIterator = self.txtRecords.cbegin(); txtIterator != self.txtRecords.cend(); txtIterator++) {
|
||||||
|
additional.append(createMdnsRecord(self, MDNS_RECORDTYPE_TXT, nullptr, txtIterator));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send the answer, unicast or multicast depending on flag in query
|
||||||
|
uint16_t unicast = (rclass & MDNS_UNICAST_RESPONSE);
|
||||||
|
printf(" --> answer %.*s port %d (%s)\n", MDNS_STRING_FORMAT(answer.data.srv.name), answer.data.srv.port, (unicast ? "unicast" : "multicast"));
|
||||||
|
|
||||||
|
if (unicast) {
|
||||||
|
mdns_query_answer_unicast(sock, from, addrlen, sendbuffer, sizeof(sendbuffer), query_id,
|
||||||
|
(mdns_record_type_t)record_type, nameMdnsString.str, nameMdnsString.length,
|
||||||
|
answer, 0, 0, additional.constData(), additional.length());
|
||||||
|
} else {
|
||||||
|
mdns_query_answer_multicast(sock, sendbuffer, sizeof(sendbuffer), answer, 0, 0,
|
||||||
|
additional.constData(), additional.length());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (name == self.hostname) {
|
||||||
|
qWarning() << "Someone queried my host for" << recordTypeToStr(record_type);
|
||||||
|
if (((record_type == MDNS_RECORDTYPE_A) || (record_type == MDNS_RECORDTYPE_ANY)) && !self.addressesV4.empty()) {
|
||||||
|
// The A query was for our qualified hostname and we have an IPv4 address, answer with an A
|
||||||
|
// record mapping the hostname to an IPv4 address, as well as an AAAA record and TXT records
|
||||||
|
|
||||||
|
mdns_record_t answer = createMdnsRecord(self, MDNS_RECORDTYPE_A, from);
|
||||||
|
|
||||||
|
QVector<mdns_record_t> additional;
|
||||||
|
if (!self.addressesV6.empty()) {
|
||||||
|
additional.append(createMdnsRecord(self, MDNS_RECORDTYPE_AAAA, from));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto txtIterator = self.txtRecords.cbegin(); txtIterator != self.txtRecords.cend(); txtIterator++) {
|
||||||
|
additional.append(createMdnsRecord(self, MDNS_RECORDTYPE_TXT, nullptr, txtIterator));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send the answer, unicast or multicast depending on flag in query
|
||||||
|
uint16_t unicast = (rclass & MDNS_UNICAST_RESPONSE);
|
||||||
|
printf(" --> answer %.*s IPv4 (%s)\n", MDNS_STRING_FORMAT(answer.name), (unicast ? "unicast" : "multicast"));
|
||||||
|
|
||||||
|
if (unicast) {
|
||||||
|
mdns_query_answer_unicast(sock, from, addrlen, sendbuffer, sizeof(sendbuffer), query_id,
|
||||||
|
(mdns_record_type_t)record_type, nameMdnsString.str, nameMdnsString.length,
|
||||||
|
answer, 0, 0, additional.constData(), additional.length());
|
||||||
|
} else {
|
||||||
|
mdns_query_answer_multicast(sock, sendbuffer, sizeof(sendbuffer), answer, 0, 0,
|
||||||
|
additional.constData(), additional.length());
|
||||||
|
}
|
||||||
|
} else if (((record_type == MDNS_RECORDTYPE_AAAA) || (record_type == MDNS_RECORDTYPE_ANY)) && !self.addressesV6.empty()) {
|
||||||
|
// The AAAA query was for our qualified hostname and we have an IPv6 address, answer with an AAAA
|
||||||
|
// record mapping the hostname to an IPv4 address, as well as an A record and TXT records
|
||||||
|
|
||||||
|
mdns_record_t answer = createMdnsRecord(self, MDNS_RECORDTYPE_AAAA, from);
|
||||||
|
|
||||||
|
QVector<mdns_record_t> additional;
|
||||||
|
if (!self.addressesV4.empty()) {
|
||||||
|
additional.append(createMdnsRecord(self, MDNS_RECORDTYPE_A, from));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto txtIterator = self.txtRecords.cbegin(); txtIterator != self.txtRecords.cend(); txtIterator++) {
|
||||||
|
additional.append(createMdnsRecord(self, MDNS_RECORDTYPE_TXT, nullptr, txtIterator));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send the answer, unicast or multicast depending on flag in query
|
||||||
|
uint16_t unicast = (rclass & MDNS_UNICAST_RESPONSE);
|
||||||
|
printf(" --> answer %.*s IPv6 (%s)\n", MDNS_STRING_FORMAT(answer.name), (unicast ? "unicast" : "multicast"));
|
||||||
|
|
||||||
|
if (unicast) {
|
||||||
|
mdns_query_answer_unicast(sock, from, addrlen, sendbuffer, sizeof(sendbuffer), query_id,
|
||||||
|
(mdns_record_type_t)record_type, nameMdnsString.str, nameMdnsString.length,
|
||||||
|
answer, 0, 0, additional.constData(), additional.length());
|
||||||
|
} else {
|
||||||
|
mdns_query_answer_multicast(sock, sendbuffer, sizeof(sendbuffer), answer, 0, 0,
|
||||||
|
additional.constData(), additional.length());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // else request is not for me
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Open sockets to listen to incoming mDNS queries on port 5353
|
||||||
|
// When recieving, each socket can recieve data from all network interfaces
|
||||||
|
// Thus we only need to open one socket for each address family
|
||||||
|
int Announcer::listenForQueries()
|
||||||
|
{
|
||||||
|
auto callback = [this](QSocketDescriptor socket) {
|
||||||
|
static char buffer[2048];
|
||||||
|
mdns_socket_listen(socket, buffer, sizeof(buffer), service_callback, &self);
|
||||||
|
};
|
||||||
|
|
||||||
|
int numSockets = 0;
|
||||||
|
|
||||||
|
{
|
||||||
|
struct sockaddr_in sock_addr;
|
||||||
|
memset(&sock_addr, 0, sizeof(struct sockaddr_in));
|
||||||
|
sock_addr.sin_family = AF_INET;
|
||||||
|
#ifdef _WIN32
|
||||||
|
sock_addr.sin_addr = in4addr_any;
|
||||||
|
#else
|
||||||
|
sock_addr.sin_addr.s_addr = INADDR_ANY;
|
||||||
|
#endif
|
||||||
|
sock_addr.sin_port = htons(MDNS_PORT);
|
||||||
|
#ifdef __APPLE__
|
||||||
|
sock_addr.sin_len = sizeof(struct sockaddr_in);
|
||||||
|
#endif
|
||||||
|
int socket = mdns_socket_open_ipv4(&sock_addr);
|
||||||
|
if (socket >= 0) {
|
||||||
|
socketNotifier = new QSocketNotifier(socket, QSocketNotifier::Read);
|
||||||
|
QObject::connect(socketNotifier, &QSocketNotifier::activated, callback);
|
||||||
|
numSockets++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
struct sockaddr_in6 sock_addr;
|
||||||
|
memset(&sock_addr, 0, sizeof(struct sockaddr_in6));
|
||||||
|
sock_addr.sin6_family = AF_INET6;
|
||||||
|
sock_addr.sin6_addr = in6addr_any;
|
||||||
|
sock_addr.sin6_port = htons(MDNS_PORT);
|
||||||
|
#ifdef __APPLE__
|
||||||
|
sock_addr.sin6_len = sizeof(struct sockaddr_in6);
|
||||||
|
#endif
|
||||||
|
int socket = mdns_socket_open_ipv6(&sock_addr);
|
||||||
|
if (socket >= 0) {
|
||||||
|
socketNotifierV6 = new QSocketNotifier(socket, QSocketNotifier::Read);
|
||||||
|
QObject::connect(socketNotifierV6, &QSocketNotifier::activated, callback);
|
||||||
|
numSockets++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return numSockets;
|
||||||
|
}
|
||||||
|
|
||||||
|
Announcer::Announcer(const QString &serviceName, const QString &serviceType, uint16_t port)
|
||||||
|
{
|
||||||
|
self.serviceType = serviceType.toLatin1();
|
||||||
|
if (!self.serviceType.endsWith('.')) {
|
||||||
|
// mdns.h needs all the qualified names to end with dot for some reason
|
||||||
|
self.serviceType.append('.');
|
||||||
|
}
|
||||||
|
self.serviceInstance = serviceName.toLatin1() + '.' + self.serviceType;
|
||||||
|
self.hostname = QHostInfo::localHostName().toLatin1() + ".local.";
|
||||||
|
self.port = port;
|
||||||
|
detectHostAddresses();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Announcer::detectHostAddresses()
|
||||||
|
{
|
||||||
|
qWarning() << "detectHostAddresses";
|
||||||
|
self.addressesV4.clear();
|
||||||
|
self.addressesV6.clear();
|
||||||
|
for (const QNetworkInterface &iface : QNetworkInterface::allInterfaces()) {
|
||||||
|
int flags = iface.flags();
|
||||||
|
if (!(flags & QNetworkInterface::IsUp) || !(flags & QNetworkInterface::CanMulticast) || (flags & QNetworkInterface::IsLoopBack)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (const QNetworkAddressEntry &ifaceAddress : iface.addressEntries()) {
|
||||||
|
QHostAddress sourceAddress = ifaceAddress.ip();
|
||||||
|
if (sourceAddress.protocol() == QAbstractSocket::IPv4Protocol && sourceAddress != QHostAddress::LocalHost) {
|
||||||
|
qWarning() << "Found ipv4" << sourceAddress;
|
||||||
|
self.addressesV4.append(sourceAddress);
|
||||||
|
} else if (sourceAddress.protocol() == QAbstractSocket::IPv6Protocol && sourceAddress != QHostAddress::LocalHostIPv6) {
|
||||||
|
qWarning() << "Found ipv6" << sourceAddress;
|
||||||
|
self.addressesV6.append(sourceAddress);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Announcer::startAnnouncing()
|
||||||
|
{
|
||||||
|
int num_sockets = listenForQueries();
|
||||||
|
if (num_sockets <= 0) {
|
||||||
|
qWarning() << "Failed to open any client sockets";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sendMulticastAnnounce(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Announcer::stopAnnouncing()
|
||||||
|
{
|
||||||
|
sendMulticastAnnounce(true);
|
||||||
|
stopListeningForQueries();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Announcer::stopListeningForQueries()
|
||||||
|
{
|
||||||
|
if (socketNotifier != nullptr) {
|
||||||
|
delete socketNotifier;
|
||||||
|
socketNotifier = nullptr;
|
||||||
|
}
|
||||||
|
if (socketNotifierV6 != nullptr) {
|
||||||
|
delete socketNotifierV6;
|
||||||
|
socketNotifierV6 = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Announcer::sendMulticastAnnounce(bool isGoodbye)
|
||||||
|
{
|
||||||
|
mdns_record_t ptr_record = createMdnsRecord(self, MDNS_RECORDTYPE_PTR);
|
||||||
|
|
||||||
|
QVector<mdns_record_t> additional;
|
||||||
|
additional.append(createMdnsRecord(self, MDNS_RECORDTYPE_SRV));
|
||||||
|
if (!self.addressesV4.empty()) {
|
||||||
|
additional.append(createMdnsRecord(self, MDNS_RECORDTYPE_A));
|
||||||
|
}
|
||||||
|
if (!self.addressesV6.empty()) {
|
||||||
|
additional.append(createMdnsRecord(self, MDNS_RECORDTYPE_AAAA));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto txtIterator = self.txtRecords.cbegin(); txtIterator != self.txtRecords.cend(); txtIterator++) {
|
||||||
|
additional.append(createMdnsRecord(self, MDNS_RECORDTYPE_TXT, nullptr, txtIterator));
|
||||||
|
}
|
||||||
|
|
||||||
|
static char buffer[2048];
|
||||||
|
if (isGoodbye) {
|
||||||
|
qCDebug(KDECONNECT_CORE) << "Sending goodbye";
|
||||||
|
if (socketNotifier) mdns_goodbye_multicast(socketNotifier->socket(), buffer, sizeof(buffer), ptr_record, 0, 0, additional.constData(), additional.length());
|
||||||
|
if (socketNotifierV6) mdns_goodbye_multicast(socketNotifierV6->socket(), buffer, sizeof(buffer), ptr_record, 0, 0, additional.constData(), additional.length());
|
||||||
|
} else {
|
||||||
|
qCDebug(KDECONNECT_CORE) << "Sending announce";
|
||||||
|
if (socketNotifier) mdns_announce_multicast(socketNotifier->socket(), buffer, sizeof(buffer), ptr_record, 0, 0, additional.constData(), additional.length());
|
||||||
|
if (socketNotifierV6) mdns_announce_multicast(socketNotifierV6->socket(), buffer, sizeof(buffer), ptr_record, 0, 0, additional.constData(), additional.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace MdnsWrapper
|
100
core/backends/lan/mdns_wrapper.h
Normal file
100
core/backends/lan/mdns_wrapper.h
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
/**
|
||||||
|
* SPDX-FileCopyrightText: 2023 Albert Vaca Cintora <albertvaka@gmail.com>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef KDECONNECT_MDNS_WRAPPER_H
|
||||||
|
#define KDECONNECT_MDNS_WRAPPER_H
|
||||||
|
|
||||||
|
#include <QHostAddress>
|
||||||
|
#include <QMap>
|
||||||
|
#include <QSocketNotifier>
|
||||||
|
#include <QString>
|
||||||
|
#include <QVector>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A Qt wrapper for the mdns.h header-only library
|
||||||
|
* from https://github.com/mjansson/mdns
|
||||||
|
*/
|
||||||
|
namespace MdnsWrapper
|
||||||
|
{
|
||||||
|
|
||||||
|
class Discoverer : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
struct MdnsService {
|
||||||
|
QString name;
|
||||||
|
uint16_t port;
|
||||||
|
QHostAddress address;
|
||||||
|
QMap<QString, QString> txtRecords;
|
||||||
|
};
|
||||||
|
|
||||||
|
// serviceType must be of the form "_<name>._<tcp/udp>.local"
|
||||||
|
void startDiscovering(const QString &serviceType);
|
||||||
|
void stopDiscovering();
|
||||||
|
|
||||||
|
void sendQuery(const QString &serviceName);
|
||||||
|
|
||||||
|
Q_SIGNALS:
|
||||||
|
void serviceFound(const MdnsService &service);
|
||||||
|
|
||||||
|
private:
|
||||||
|
int listenForQueryResponses();
|
||||||
|
void stopListeningForQueryResponses();
|
||||||
|
|
||||||
|
QVector<QSocketNotifier *> responseSocketNotifiers;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Announcer : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
struct AnnouncedInfo {
|
||||||
|
QByteArray serviceType; // ie: "<_service-type>._tcp.local."
|
||||||
|
QByteArray serviceInstance; // ie: "<service-name>.<_service-type>._tcp.local."
|
||||||
|
QByteArray hostname; // ie: "<hostname>.local."
|
||||||
|
QVector<QHostAddress> addressesV4;
|
||||||
|
QVector<QHostAddress> addressesV6;
|
||||||
|
uint16_t port;
|
||||||
|
QHash<QByteArray, QByteArray> txtRecords;
|
||||||
|
};
|
||||||
|
|
||||||
|
// serviceType must be of the form "_<name>._<tcp/udp>.local"
|
||||||
|
Announcer(const QString &serviceName, const QString &serviceType, uint16_t port);
|
||||||
|
|
||||||
|
void putTxtRecord(const QString &key, const QString &value)
|
||||||
|
{
|
||||||
|
self.txtRecords[key.toLatin1()] = value.toLatin1();
|
||||||
|
}
|
||||||
|
|
||||||
|
void startAnnouncing();
|
||||||
|
void stopAnnouncing();
|
||||||
|
|
||||||
|
void sendMulticastAnnounce(bool isGoodbye);
|
||||||
|
|
||||||
|
public Q_SLOTS:
|
||||||
|
// notify that network interfaces changed since the creation of this Announcer
|
||||||
|
void onNetworkChange()
|
||||||
|
{
|
||||||
|
detectHostAddresses();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
int listenForQueries();
|
||||||
|
void stopListeningForQueries();
|
||||||
|
|
||||||
|
void detectHostAddresses();
|
||||||
|
|
||||||
|
AnnouncedInfo self;
|
||||||
|
|
||||||
|
QSocketNotifier *socketNotifier = nullptr;
|
||||||
|
QSocketNotifier *socketNotifierV6 = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace MdnsWrapper
|
||||||
|
|
||||||
|
#endif
|
61
core/backends/lan/mdnsdiscovery.cpp
Normal file
61
core/backends/lan/mdnsdiscovery.cpp
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
/**
|
||||||
|
* SPDX-FileCopyrightText: 2023 Albert Vaca <albertvaka@gmail.com>
|
||||||
|
*
|
||||||
|
* 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 "mdns_wrapper.h"
|
||||||
|
|
||||||
|
QString kServiceType = QStringLiteral("_kdeconnect._udp.local");
|
||||||
|
|
||||||
|
MdnsDiscovery::MdnsDiscovery(LanLinkProvider *lanLinkProvider)
|
||||||
|
: lanLinkProvider(lanLinkProvider)
|
||||||
|
, mdnsAnnouncer(KdeConnectConfig::instance().deviceId(), kServiceType, LanLinkProvider::UDP_PORT)
|
||||||
|
{
|
||||||
|
KdeConnectConfig &config = KdeConnectConfig::instance();
|
||||||
|
mdnsAnnouncer.putTxtRecord(QStringLiteral("id"), config.deviceId());
|
||||||
|
mdnsAnnouncer.putTxtRecord(QStringLiteral("name"), config.name());
|
||||||
|
mdnsAnnouncer.putTxtRecord(QStringLiteral("type"), config.deviceType().toString());
|
||||||
|
mdnsAnnouncer.putTxtRecord(QStringLiteral("protocol"), QString::number(NetworkPacket::s_protocolVersion));
|
||||||
|
|
||||||
|
connect(&mdnsDiscoverer, &MdnsWrapper::Discoverer::serviceFound, [lanLinkProvider](const MdnsWrapper::Discoverer::MdnsService &service) {
|
||||||
|
if (KdeConnectConfig::instance().deviceId() == service.name) {
|
||||||
|
qCDebug(KDECONNECT_CORE) << "Discovered myself, ignoring";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
lanLinkProvider->sendUdpIdentityPacket(QList<QHostAddress>{service.address});
|
||||||
|
qCDebug(KDECONNECT_CORE) << "Discovered" << service.name << "at" << service.address;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
MdnsDiscovery::~MdnsDiscovery()
|
||||||
|
{
|
||||||
|
stopAnnouncing();
|
||||||
|
stopDiscovering();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MdnsDiscovery::startAnnouncing()
|
||||||
|
{
|
||||||
|
mdnsAnnouncer.startAnnouncing();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MdnsDiscovery::stopAnnouncing()
|
||||||
|
{
|
||||||
|
mdnsAnnouncer.stopAnnouncing();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MdnsDiscovery::startDiscovering()
|
||||||
|
{
|
||||||
|
mdnsDiscoverer.startDiscovering(kServiceType);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MdnsDiscovery::stopDiscovering()
|
||||||
|
{
|
||||||
|
mdnsDiscoverer.stopDiscovering();
|
||||||
|
}
|
44
core/backends/lan/mdnsdiscovery.h
Normal file
44
core/backends/lan/mdnsdiscovery.h
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
/**
|
||||||
|
* SPDX-FileCopyrightText: 2023 Albert Vaca Cintora <albertvaka@gmail.com>
|
||||||
|
*
|
||||||
|
* 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 <QObject>
|
||||||
|
|
||||||
|
#include "kdeconnectcore_export.h"
|
||||||
|
|
||||||
|
#include "mdns_wrapper.h"
|
||||||
|
|
||||||
|
class LanLinkProvider;
|
||||||
|
|
||||||
|
class KDECONNECTCORE_EXPORT MdnsDiscovery : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit MdnsDiscovery(LanLinkProvider *parent);
|
||||||
|
~MdnsDiscovery();
|
||||||
|
|
||||||
|
void startDiscovering();
|
||||||
|
void stopDiscovering();
|
||||||
|
|
||||||
|
void stopAnnouncing();
|
||||||
|
void startAnnouncing();
|
||||||
|
|
||||||
|
public Q_SLOTS:
|
||||||
|
void onNetworkChange()
|
||||||
|
{
|
||||||
|
mdnsAnnouncer.onNetworkChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
LanLinkProvider *lanLinkProvider = nullptr;
|
||||||
|
MdnsWrapper::Discoverer mdnsDiscoverer;
|
||||||
|
MdnsWrapper::Announcer mdnsAnnouncer;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // KDECONNECT_SERVER_H
|
Loading…
Reference in a new issue