From 3a39583704aac13c651a7c3853e0868fa4de85df Mon Sep 17 00:00:00 2001 From: Holden Date: Mon, 3 Jun 2024 22:34:33 -0400 Subject: [PATCH] Comms: UDPLink Threading Changes --- src/Comms/CMakeLists.txt | 2 +- src/Comms/LinkManager.cc | 33 +- src/Comms/UDPLink.cc | 809 ++++++++++++--------- src/Comms/UDPLink.h | 208 ++++-- tools/setup/install-dependencies-debian.sh | 5 +- 5 files changed, 636 insertions(+), 421 deletions(-) diff --git a/src/Comms/CMakeLists.txt b/src/Comms/CMakeLists.txt index c97045c9859..0f33c5516ce 100644 --- a/src/Comms/CMakeLists.txt +++ b/src/Comms/CMakeLists.txt @@ -109,7 +109,7 @@ if(QGC_ZEROCONF_ENABLED) ) FetchContent_MakeAvailable(qmdnsengine) - target_link_libraries(Comms PRIVATE qmdnsengine) + target_link_libraries(Comms PUBLIC qmdnsengine) target_compile_definitions(Comms PUBLIC QGC_ZEROCONF_ENABLED) endif() diff --git a/src/Comms/LinkManager.cc b/src/Comms/LinkManager.cc index 9c0981c89ef..81c2cf0f174 100644 --- a/src/Comms/LinkManager.cc +++ b/src/Comms/LinkManager.cc @@ -428,8 +428,8 @@ void LinkManager::_addZeroConfAutoConnectLink() server.reset(new QMdnsEngine::Server()); browser.reset(new QMdnsEngine::Browser(server.get(), QMdnsEngine::MdnsBrowseType)); - auto checkIfConnectionLinkExist = [this](LinkConfiguration::LinkType linkType, const QString &linkName) { - for (const SharedLinkConfigurationPtr &link : std::as_const(_rgLinks)) { + const auto checkIfConnectionLinkExist = [this](LinkConfiguration::LinkType linkType, const QString &linkName) { + for (const SharedLinkInterfacePtr &link : std::as_const(_rgLinks)) { const SharedLinkConfigurationPtr linkConfig = link->linkConfiguration(); if ((linkConfig->type() == linkType) && (linkConfig->name() == linkName)) { return true; @@ -440,13 +440,14 @@ void LinkManager::_addZeroConfAutoConnectLink() }; (void) connect(browser.get(), &QMdnsEngine::Browser::serviceAdded, this, [checkIfConnectionLinkExist, this](const QMdnsEngine::Service &service) { - qCDebug(LinkManagerVerboseLog) << "Found Zero-Conf:" << service.type() << service.name() << service.hostname() << service.port() << service.attributes(); + qCDebug(LinkManagerLog) << "Found Zero-Conf:" << service.type() << service.name() << service.hostname() << service.port() << service.attributes(); if (!service.type().startsWith("_mavlink")) { + qCWarning(LinkManagerLog) << "Invalid ZeroConf SericeType" << service.type(); return; } - // Windows dont accept trailling dots in mdns + // Windows doesnt accept trailling dots in mdns // http://www.dns-sd.org/trailingdotsindomainnames.html QString hostname = service.hostname(); if (hostname.endsWith('.')) { @@ -456,34 +457,34 @@ void LinkManager::_addZeroConfAutoConnectLink() if (service.type().startsWith("_mavlink._udp")) { static const QString udpName = QStringLiteral("ZeroConf UDP"); if (checkIfConnectionLinkExist(LinkConfiguration::TypeUdp, udpName)) { - qCDebug(LinkManagerVerboseLog) << "Connection already exist"; + qCDebug(LinkManagerLog) << "Connection already exist"; return; } - UDPConfiguration link = new UDPConfiguration(udpName); + UDPConfiguration *const link = new UDPConfiguration(udpName); link->addHost(hostname, service.port()); link->setAutoConnect(true); link->setDynamic(true); SharedLinkConfigurationPtr config = addConfiguration(link); - createConnectedLink(config); - return; - } - - if (service.type().startsWith("_mavlink._tcp")) { - static QString tcpName("ZeroConf TCP"); + if (!createConnectedLink(config)) { + qCWarning(LinkManagerLog) << "Failed to create" << udpName; + } + } else if (service.type().startsWith("_mavlink._tcp")) { + static QString tcpName = QStringLiteral("ZeroConf TCP"); if (checkIfConnectionLinkExist(LinkConfiguration::TypeTcp, tcpName)) { - qCDebug(LinkManagerVerboseLog) << "Connection already exist"; + qCDebug(LinkManagerLog) << "Connection already exist"; return; } - TCPConfiguration link = new TCPConfiguration(tcpName); + TCPConfiguration *const link = new TCPConfiguration(tcpName); link->setHost(hostname); link->setPort(service.port()); link->setAutoConnect(true); link->setDynamic(true); SharedLinkConfigurationPtr config = addConfiguration(link); - createConnectedLink(config); - return; + if (!createConnectedLink(config)) { + qCWarning(LinkManagerLog) << "Failed to create" << tcpName; + } } }); } diff --git a/src/Comms/UDPLink.cc b/src/Comms/UDPLink.cc index 352d0883b5c..22ece1616c6 100644 --- a/src/Comms/UDPLink.cc +++ b/src/Comms/UDPLink.cc @@ -8,455 +8,616 @@ ****************************************************************************/ #include "UDPLink.h" -#include "QGCApplication.h" -#include "SettingsManager.h" #include "AutoConnectSettings.h" #include "DeviceInfo.h" +#include "QGCLoggingCategory.h" +#include "SettingsManager.h" -#include #include -#include -#include #include +#include +#include +#include #include -static bool is_ip(const QString& address) -{ - int a,b,c,d; - if (sscanf(address.toStdString().c_str(), "%d.%d.%d.%d", &a, &b, &c, &d) != 4 && strcmp("::1", address.toStdString().c_str())) { +QGC_LOGGING_CATEGORY(UDPLinkLog, "qgc.comms.udplink") + +namespace { + bool containsTarget(const QList> &list, const QHostAddress &address, quint16 port) + { + for (const std::shared_ptr &target : list) { + if ((target->address == address) && (target->port == port)) { + return true; + } + } + return false; + } +} + +/*===========================================================================*/ + +UDPConfiguration::UDPConfiguration(const QString &name, QObject *parent) + : LinkConfiguration(name, parent) +{ + AutoConnectSettings *const settings = SettingsManager::instance()->autoConnectSettings(); + setLocalPort(settings->udpListenPort()->rawValue().toInt()); + + const QString targetHostIP = settings->udpTargetHostIP()->rawValue().toString(); + if (!targetHostIP.isEmpty()) { + const quint16 targetHostPort = settings->udpTargetHostPort()->rawValue().toUInt(); + addHost(targetHostIP, targetHostPort); + } +} + +UDPConfiguration::UDPConfiguration(const UDPConfiguration *source, QObject *parent) + : LinkConfiguration(source, parent) +{ + // qCDebug(UDPLinkLog) << Q_FUNC_INFO << this; + + UDPConfiguration::copyFrom(source); +} + +UDPConfiguration::~UDPConfiguration() +{ + _targetHosts.clear(); + + // qCDebug(UDPLinkLog) << Q_FUNC_INFO << this; +} + +void UDPConfiguration::copyFrom(const LinkConfiguration *source) +{ + Q_ASSERT(source); + LinkConfiguration::copyFrom(source); + + const UDPConfiguration *const udpSource = qobject_cast(source); + Q_ASSERT(udpSource); + + setLocalPort(udpSource->localPort()); + _targetHosts.clear(); + + for (const std::shared_ptr &target : udpSource->targetHosts()) { + if (!_targetHosts.contains(target)) { + _targetHosts.append(std::make_shared(target.get())); + _updateHostList(); + } + } +} + +void UDPConfiguration::loadSettings(QSettings &settings, const QString &root) +{ + settings.beginGroup(root); + + setLocalPort(static_cast(settings.value("port", SettingsManager::instance()->autoConnectSettings()->udpListenPort()->rawValue().toUInt()).toUInt())); + + _targetHosts.clear(); + const qsizetype hostCount = settings.value("hostCount", 0).toUInt(); + for (qsizetype i = 0; i < hostCount; i++) { + const QString hkey = QStringLiteral("host%1").arg(i); + const QString pkey = QStringLiteral("port%1").arg(i); + if (settings.contains(hkey) && settings.contains(pkey)) { + addHost(settings.value(hkey).toString(), settings.value(pkey).toUInt()); + } + } + + _updateHostList(); + + settings.endGroup(); +} + +void UDPConfiguration::saveSettings(QSettings &settings, const QString &root) +{ + settings.beginGroup(root); + + settings.setValue(QStringLiteral("hostCount"), _targetHosts.size()); + settings.setValue(QStringLiteral("port"), _localPort); + + for (qsizetype i = 0; i < _targetHosts.size(); i++) { + const std::shared_ptr target = _targetHosts.at(i); + const QString hkey = QStringLiteral("host%1").arg(i); + settings.setValue(hkey, target->address.toString()); + const QString pkey = QStringLiteral("port%1").arg(i); + settings.setValue(pkey, target->port); + } + + settings.endGroup(); +} + +void UDPConfiguration::addHost(const QString &host) +{ + if (host.contains(":")) { + const QStringList hostInfo = host.split(":"); + if (hostInfo.size() != 2) { + qCWarning(UDPLinkLog) << "Invalid host format:" << host; + return; + } + + const QString address = hostInfo.constFirst(); + const quint16 port = hostInfo.constLast().toUInt(); + + addHost(address, port); } else { - return true; + addHost(host, _localPort); } } -static QString get_ip_address(const QString& address) +void UDPConfiguration::addHost(const QString &host, quint16 port) { - if (is_ip(address)) { - return address; + const QString ipAdd = _getIpAddress(host); + if (ipAdd.isEmpty()) { + qCWarning(UDPLinkLog) << "Could not resolve host:" << host << "port:" << port; + return; + } + + const QHostAddress address(ipAdd); + if (!containsTarget(_targetHosts, address, port)) { + _targetHosts.append(std::make_shared(address, port)); + _updateHostList(); } - // Need to look it up - QHostInfo info = QHostInfo::fromName(address); - if (info.error() == QHostInfo::NoError) { - QList hostAddresses = info.addresses(); - for (int i=0; i> it(_targetHosts); + while (it.hasNext()) { + std::shared_ptr target = it.next(); + if ((target->address == address) && (target->port == port)) { + target.reset(); + it.remove(); + _updateHostList(); + return; } } + } else { + removeHost(host, _localPort); } - return QString(); } -static bool contains_target(const QList list, const QHostAddress& address, quint16 port) +void UDPConfiguration::removeHost(const QString &host, quint16 port) { - for (int i=0; iaddress == address && target->port == port) { - return true; + const QString ipAdd = _getIpAddress(host); + if (ipAdd.isEmpty()) { + qCWarning(UDPLinkLog) << "Could not resolve host:" << host << "port:" << port; + return; + } + + const QHostAddress address(ipAdd); + if (!containsTarget(_targetHosts, address, port)) { + qCWarning(UDPLinkLog) << "Could not remove unknown host:" << host << "port:" << port; + return; + } + + QMutableListIterator> it(_targetHosts); + while (it.hasNext()) { + std::shared_ptr target = it.next(); + if ((target->address == address) && (target->port == port)) { + target.reset(); + it.remove(); + _updateHostList(); + return; } } - return false; } -UDPLink::UDPLink(SharedLinkConfigurationPtr& config) - : LinkInterface (config) - , _running (false) - , _socket (nullptr) - , _udpConfig (qobject_cast(config.get())) - , _connectState (false) -#if defined(QGC_ZEROCONF_ENABLED) - , _dnssServiceRef (nullptr) -#endif +void UDPConfiguration::_updateHostList() { - if (!_udpConfig) { - qWarning() << "Internal error"; + _hostList.clear(); + for (const std::shared_ptr &target : _targetHosts) { + const QString host = target->address.toString() + ":" + QString::number(target->port); + _hostList.append(host); } - auto allAddresses = QNetworkInterface::allAddresses(); - for (int i=0; i hostAddresses = info.addresses(); + for (const QHostAddress &hostAddress : hostAddresses) { + if (hostAddress.protocol() == QAbstractSocket::NetworkLayerProtocol::IPv4Protocol) { + return hostAddress.toString(); + } + } + + return QString(); } -UDPLink::~UDPLink() +/*===========================================================================*/ + +const QHostAddress UDPWorker::_multicastGroup = QHostAddress(QStringLiteral("224.0.0.1")); + +UDPWorker::UDPWorker(const UDPConfiguration *config, QObject *parent) + : QObject(parent) + , _udpConfig(config) + , _socket(new QUdpSocket(this)) { - disconnect(); - // Tell the thread to exit - _running = false; - // Clear client list - qDeleteAll(_sessionTargets); - _sessionTargets.clear(); - quit(); - // Wait for it to exit - wait(); - this->deleteLater(); + // qCDebug(UDPLinkLog) << Q_FUNC_INFO << this; +} + +UDPWorker::~UDPWorker() +{ + disconnectLink(); + + // qCDebug(UDPLinkLog) << Q_FUNC_INFO << this; +} + +bool UDPWorker::isConnected() const +{ + return (_socket->isValid() && _isConnected); +} + +void UDPWorker::setupSocket() +{ + _socket->setProxy(QNetworkProxy::NoProxy); + + (void) connect(_socket, &QUdpSocket::connected, this, &UDPWorker::_onSocketConnected); + (void) connect(_socket, &QUdpSocket::disconnected, this, &UDPWorker::_onSocketDisconnected); + (void) connect(_socket, &QUdpSocket::readyRead, this, &UDPWorker::_onSocketReadyRead); + (void) connect(_socket, &QUdpSocket::errorOccurred, this, &UDPWorker::_onSocketErrorOccurred); + (void) connect(_socket, &QUdpSocket::stateChanged, this, [this](QUdpSocket::SocketState state) { + qCDebug(UDPLinkLog) << "UDP State Changed:" << state; + switch (state) { + case QAbstractSocket::BoundState: + _onSocketConnected(); + break; + case QAbstractSocket::ClosingState: + case QAbstractSocket::UnconnectedState: + _onSocketDisconnected(); + break; + default: + break; + } + }); + + if (UDPLinkLog().isDebugEnabled()) { + // (void) connect(_socket, &QUdpSocket::bytesWritten, this, &UDPWorker::_onSocketBytesWritten); + + (void) QObject::connect(_socket, &QUdpSocket::hostFound, this, []() { + qCDebug(UDPLinkLog) << "UDP Host Found"; + }); + } } -void UDPLink::run() +void UDPWorker::connectLink() { - if (_hardwareConnect()) { - exec(); + if (isConnected()) { + qCWarning(UDPLinkLog) << "Already connected to" << _udpConfig->localPort(); + return; } - if (_socket) { - _deregisterZeroconf(); - _socket->close(); + + qCDebug(UDPLinkLog) << "Attempting to bind to port:" << _udpConfig->localPort(); + const bool bindSuccess = _socket->bind(QHostAddress::AnyIPv4, _udpConfig->localPort(), QAbstractSocket::ReuseAddressHint | QAbstractSocket::ShareAddress); + if (!bindSuccess) { + emit errorOccurred(tr("Failed to bind UDP socket to port")); + qCWarning(UDPLinkLog) << "Failed to bind UDP socket to port" << _udpConfig->localPort() << "with no error signal."; + _onSocketDisconnected(); + return; } + + qCDebug(UDPLinkLog) << "Attempting to join multicast group:" << _multicastGroup.toString(); + const bool joinSuccess = _socket->joinMulticastGroup(_multicastGroup); + if (!joinSuccess) { + emit errorOccurred(tr("Failed to join multicast group")); + qCWarning(UDPLinkLog) << "Failed to join multicast group" << _multicastGroup.toString(); + _onSocketDisconnected(); + return; + } + +#ifdef QGC_ZEROCONF_ENABLED + _registerZeroconf(_udpConfig->localPort()); +#endif + + qCDebug(UDPLinkLog) << "Successfully bound to port" << _udpConfig->localPort() << "and joined multicast group."; + return; } -bool UDPLink::_isIpLocal(const QHostAddress& add) -{ - // In simulation and testing setups the vehicle and the GCS can be - // running on the same host. This leads to packets arriving through - // the local network or the loopback adapter, which makes it look - // like the vehicle is connected through two different links, - // complicating routing. - // - // We detect this case and force all traffic to a simulated instance - // onto the local loopback interface. - // Run through all IPv4 interfaces and check if their canonical - // IP address in string representation matches the source IP address - // - // On Windows, this is a very expensive call only Redmond would know - // why. As such, we make it once and keep the list locally. If a new - // interface shows up after we start, it won't be on this list. - for (int i=0; i<_localAddresses.count(); i++) { - QHostAddress &address = _localAddresses[i]; - if (address == add) { - // This is a local address of the same host - return true; - } +void UDPWorker::disconnectLink() +{ +#ifdef QGC_ZEROCONF_ENABLED + _deregisterZeroconf(); +#endif + + if (isConnected()) { + (void) _socket->leaveMulticastGroup(_multicastGroup); + _socket->close(); } - return false; + + _sessionTargets.clear(); } -void UDPLink::_writeBytes(const QByteArray &data) +void UDPWorker::writeData(const QByteArray &data) { - if (!_socket) { + if (!_socket->isValid()) { + emit errorOccurred(tr("Could Not Send Data - Socket is Invalid!")); + return; + } + + if (!isConnected()) { + emit errorOccurred(tr("Could Not Send Data - Link is Disconnected!")); return; } - emit bytesSent(this, data); QMutexLocker locker(&_sessionTargetsMutex); // Send to all manually targeted systems - for (int i=0; i<_udpConfig->targetHosts().count(); i++) { - UDPCLient* target = _udpConfig->targetHosts()[i]; - // Skip it if it's part of the session clients below - if(!contains_target(_sessionTargets, target->address, target->port)) { - _writeDataGram(data, target); + for (const std::shared_ptr &target : _udpConfig->targetHosts()) { + if (!_sessionTargets.contains(target)) { + if (_socket->writeDatagram(data, target->address, target->port) < 0) { + emit errorOccurred(tr("Could Not Send Data - Write Failed!")); + } } } + // Send to all connected systems - for(UDPCLient* target: _sessionTargets) { - _writeDataGram(data, target); + for (const std::shared_ptr &target: _sessionTargets) { + if (_socket->writeDatagram(data, target->address, target->port) < 0) { + emit errorOccurred(tr("Could Not Send Data - Write Failed!")); + } } + + locker.unlock(); + + emit dataSent(data); } -void UDPLink::_writeDataGram(const QByteArray data, const UDPCLient* target) +void UDPWorker::_onSocketConnected() { - //qDebug() << "UDP Out" << target->address << target->port; - if(_socket->writeDatagram(data, target->address, target->port) < 0) { - qWarning() << "Error writing to" << target->address << target->port; - } + qCDebug(UDPLinkLog) << "UDP connected to" << _udpConfig->localPort(); + _isConnected = true; + emit connected(); +} + +void UDPWorker::_onSocketDisconnected() +{ + qCDebug(UDPLinkLog) << "UDP disconnected from" << _udpConfig->localPort(); + _isConnected = false; + emit disconnected(); } -void UDPLink::readBytes() +void UDPWorker::_onSocketReadyRead() { - if (!_socket) { + if (!_socket->isValid()) { + emit errorOccurred(tr("Socket is Invalid!")); return; } - QByteArray databuffer; - while (_socket->hasPendingDatagrams()) - { - QByteArray datagram; - datagram.resize(_socket->pendingDatagramSize()); - QHostAddress sender; - quint16 senderPort; - // If the other end is reset then it will still report data available, - // but will fail on the readDatagram call - qint64 slen = _socket->readDatagram(datagram.data(), datagram.size(), &sender, &senderPort); - if (slen == -1) { - break; - } - databuffer.append(datagram); - //-- Wait a bit before sending it over - if (databuffer.size() > 10 * 1024) { - emit bytesReceived(this, databuffer); - databuffer.clear(); + + if (!isConnected()) { + emit errorOccurred(tr("Could Not Read Data - Link is Disconnected!")); + return; + } + + const qint64 byteCount = _socket->pendingDatagramSize(); + if (byteCount <= 0) { + emit errorOccurred(tr("Could Not Read Data - No Data Available!")); + return; + } + + QByteArray buffer; + QElapsedTimer timer; + timer.start(); + while (_socket->hasPendingDatagrams()) { + const QNetworkDatagram datagramIn = _socket->receiveDatagram(); + if (datagramIn.isNull() || datagramIn.data().isEmpty()) { + continue; } - // TODO: This doesn't validade the sender. Anything sending UDP packets to this port gets - // added to the list and will start receiving datagrams from here. Even a port scanner - // would trigger this. - // Add host to broadcast list if not yet present, or update its port - QHostAddress asender = sender; - if(_isIpLocal(sender)) { - asender = QHostAddress(QString("127.0.0.1")); + + (void) buffer.append(datagramIn.data()); + + if (buffer.size() > (10 * 1024) || (timer.elapsed() > 50)) { + emit dataReceived(buffer); + buffer.clear(); + (void) timer.restart(); } + + const QList localAddresses = QNetworkInterface::allAddresses(); + const bool ipLocal = datagramIn.senderAddress().isLoopback() || localAddresses.contains(datagramIn.senderAddress()); + const QHostAddress senderAddress = ipLocal ? QHostAddress(QHostAddress::SpecialAddress::LocalHost) : datagramIn.senderAddress(); + QMutexLocker locker(&_sessionTargetsMutex); - if (!contains_target(_sessionTargets, asender, senderPort)) { - qDebug() << "Adding target" << asender << senderPort; - UDPCLient* target = new UDPCLient(asender, senderPort); - _sessionTargets.append(target); + if (!containsTarget(_sessionTargets, senderAddress, datagramIn.senderPort())) { + qCDebug(UDPLinkLog) << "UDP Adding target:" << senderAddress << datagramIn.senderPort(); + (void) _sessionTargets.append(std::make_shared(senderAddress, datagramIn.senderPort())); } locker.unlock(); } - //-- Send whatever is left - if (databuffer.size()) { - emit bytesReceived(this, databuffer); + + if (buffer.isEmpty()) { + emit errorOccurred(tr("No Data Available to Read!")); + return; } + + emit dataReceived(buffer); } -void UDPLink::disconnect(void) +void UDPWorker::_onSocketBytesWritten(qint64 bytes) { - _running = false; - quit(); - wait(); - if (_socket) { - // This prevents stale signal from calling the link after it has been deleted - QObject::disconnect(_socket, &QUdpSocket::readyRead, this, &UDPLink::readBytes); - // Make sure delete happen on correct thread - _socket->deleteLater(); - _socket = nullptr; - emit disconnected(); - } - _connectState = false; + qCDebug(UDPLinkLog) << "Wrote" << bytes << "bytes"; } -bool UDPLink::_connect(void) +void UDPWorker::_onSocketErrorOccurred(QUdpSocket::SocketError error) { - if (this->isRunning() || _running) { - _running = false; - quit(); - wait(); - } - _running = true; - start(NormalPriority); - return true; + qCWarning(UDPLinkLog) << "UDP Link error:" << error << _socket->errorString(); + emit errorOccurred(_socket->errorString()); } -bool UDPLink::_hardwareConnect() +#ifdef QGC_ZEROCONF_ENABLED +void UDPWorker::_zeroconfRegisterCallback(DNSServiceRef sdRef, DNSServiceFlags flags, DNSServiceErrorType errorCode, const char *name, const char *regtype, const char *domain, void *context) { - if (_socket) { - delete _socket; - _socket = nullptr; - } - QHostAddress host = QHostAddress::AnyIPv4; - _socket = new QUdpSocket(this); - _socket->setProxy(QNetworkProxy::NoProxy); - _connectState = _socket->bind(host, _udpConfig->localPort(), QAbstractSocket::ReuseAddressHint | QUdpSocket::ShareAddress); - if (_connectState) { - _socket->joinMulticastGroup(QHostAddress("224.0.0.1")); - //-- Make sure we have a large enough IO buffers -#ifdef __mobile__ - _socket->setSocketOption(QAbstractSocket::SendBufferSizeSocketOption, 64 * 1024); - _socket->setSocketOption(QAbstractSocket::ReceiveBufferSizeSocketOption, 128 * 1024); -#else - _socket->setSocketOption(QAbstractSocket::SendBufferSizeSocketOption, 256 * 1024); - _socket->setSocketOption(QAbstractSocket::ReceiveBufferSizeSocketOption, 512 * 1024); -#endif - _registerZeroconf(_udpConfig->localPort(), kZeroconfRegistration); - QObject::connect(_socket, &QUdpSocket::readyRead, this, &UDPLink::readBytes); - emit connected(); - } else { - emit communicationError(tr("UDP Link Error"), tr("Error binding UDP port: %1").arg(_socket->errorString())); + Q_UNUSED(sdRef); Q_UNUSED(flags); Q_UNUSED(name); Q_UNUSED(regtype); Q_UNUSED(domain); + + // qCDebug(UDPLinkLog) << Q_FUNC_INFO; + + UDPWorker *const worker = static_cast(context); + if (errorCode != kDNSServiceErr_NoError) { + emit worker->errorOccurred(tr("Zeroconf Register Error: %1").arg(errorCode)); } - return _connectState; } -bool UDPLink::isConnected() const +void UDPWorker::_registerZeroconf(uint16_t port) { - return _connectState; -} + static constexpr const char *regType = "_qgroundcontrol._udp"; -void UDPLink::_registerZeroconf(uint16_t port, const std::string ®Type) -{ -#if defined(QGC_ZEROCONF_ENABLED) - DNSServiceErrorType result = DNSServiceRegister(&_dnssServiceRef, 0, 0, 0, - regType.c_str(), - NULL, - NULL, - htons(port), - 0, - NULL, - NULL, - NULL); - if (result != kDNSServiceErr_NoError) - { - emit communicationError(tr("UDP Link Error"), tr("Error registering Zeroconf")); + if (_dnssServiceRef) { + qCWarning(UDPLinkLog) << "Already registered zeroconf"; + return; + } + + const DNSServiceErrorType result = DNSServiceRegister( + &_dnssServiceRef, + 0, + 0, + 0, + regType, + NULL, + NULL, + qToBigEndian(port), + 0, + NULL, + &UDPWorker::_zeroconfRegisterCallback, + this + ); + + if (result != kDNSServiceErr_NoError) { _dnssServiceRef = NULL; + emit errorOccurred(tr("Error Registering Zeroconf: %1").arg(result)); + return; } -#else - Q_UNUSED(port); - Q_UNUSED(regType); -#endif + + const int sockfd = DNSServiceRefSockFD(_dnssServiceRef); + if (sockfd == -1) { + emit errorOccurred(tr("Invalid sockfd")); + return; + } + + QSocketNotifier *const socketNotifier = new QSocketNotifier(sockfd, QSocketNotifier::Read, this); + (void) connect(socketNotifier, &QSocketNotifier::activated, this, [this, socketNotifier]() { + const DNSServiceErrorType error = DNSServiceProcessResult(_dnssServiceRef); + if (error != kDNSServiceErr_NoError) { + emit errorOccurred(tr("DNSServiceProcessResult Error: %1").arg(error)); + } + socketNotifier->deleteLater(); + }); } -void UDPLink::_deregisterZeroconf() +void UDPWorker::_deregisterZeroconf() { -#if defined(QGC_ZEROCONF_ENABLED) - if (_dnssServiceRef) - { + if (_dnssServiceRef) { DNSServiceRefDeallocate(_dnssServiceRef); _dnssServiceRef = NULL; } -#endif } +#endif // QGC_ZEROCONF_ENABLED -bool UDPLink::isSecureConnection() +/*===========================================================================*/ + +UDPLink::UDPLink(SharedLinkConfigurationPtr &config, QObject *parent) + : LinkInterface(config, parent) + , _udpConfig(qobject_cast(config.get())) + , _worker(new UDPWorker(_udpConfig)) + , _workerThread(new QThread(this)) { - return QGCDeviceInfo::isNetworkWired(); -} + // qCDebug(UDPLinkLog) << Q_FUNC_INFO << this; -//-------------------------------------------------------------------------- -//-- UDPConfiguration + _workerThread->setObjectName(QStringLiteral("UDP_%1").arg(_udpConfig->name())); -UDPConfiguration::UDPConfiguration(const QString& name) : LinkConfiguration(name) -{ - AutoConnectSettings* settings = SettingsManager::instance()->autoConnectSettings(); - _localPort = settings->udpListenPort()->rawValue().toInt(); - QString targetHostIP = settings->udpTargetHostIP()->rawValue().toString(); - if (!targetHostIP.isEmpty()) { - addHost(targetHostIP, settings->udpTargetHostPort()->rawValue().toUInt()); - } -} + _worker->moveToThread(_workerThread); -UDPConfiguration::UDPConfiguration(const UDPConfiguration* source) : LinkConfiguration(source) -{ - _copyFrom(source); + (void) connect(_workerThread, &QThread::started, _worker, &UDPWorker::setupSocket); + (void) connect(_workerThread, &QThread::finished, _worker, &QObject::deleteLater); + + (void) connect(_worker, &UDPWorker::connected, this, &UDPLink::_onConnected, Qt::QueuedConnection); + (void) connect(_worker, &UDPWorker::disconnected, this, &UDPLink::_onDisconnected, Qt::QueuedConnection); + (void) connect(_worker, &UDPWorker::errorOccurred, this, &UDPLink::_onErrorOccurred, Qt::QueuedConnection); + (void) connect(_worker, &UDPWorker::dataReceived, this, &UDPLink::_onDataReceived, Qt::QueuedConnection); + (void) connect(_worker, &UDPWorker::dataSent, this, &UDPLink::_onDataSent, Qt::QueuedConnection); + + _workerThread->start(); } -UDPConfiguration::~UDPConfiguration() +UDPLink::~UDPLink() { - _clearTargetHosts(); + UDPLink::disconnect(); + + _workerThread->quit(); + if (!_workerThread->wait()) { + qCWarning(UDPLinkLog) << "Failed to wait for UDP Thread to close"; + } + + // qCDebug(UDPLinkLog) << Q_FUNC_INFO << this; } -void UDPConfiguration::copyFrom(const LinkConfiguration *source) +bool UDPLink::isConnected() const { - LinkConfiguration::copyFrom(source); - _copyFrom(source); + return _worker->isConnected(); } -void UDPConfiguration::_copyFrom(const LinkConfiguration *source) +bool UDPLink::_connect() { - const UDPConfiguration* usource = qobject_cast(source); - if (usource) { - _localPort = usource->localPort(); - _clearTargetHosts(); - for (int i=0; itargetHosts().count(); i++) { - UDPCLient* target = usource->targetHosts()[i]; - if(!contains_target(_targetHosts, target->address, target->port)) { - UDPCLient* newTarget = new UDPCLient(target); - _targetHosts.append(newTarget); - _updateHostList(); - } - } - } else { - qWarning() << "Internal error"; - } + return QMetaObject::invokeMethod(_worker, "connectLink", Qt::QueuedConnection); } -void UDPConfiguration::_clearTargetHosts() +void UDPLink::disconnect() { - qDeleteAll(_targetHosts); - _targetHosts.clear(); + (void) QMetaObject::invokeMethod(_worker, "disconnectLink", Qt::QueuedConnection); } -/** - * @param host Hostname in standard formatt, e.g. localhost:14551 or 192.168.1.1:14551 - */ -void UDPConfiguration::addHost(const QString host) +void UDPLink::_onConnected() { - // Handle x.x.x.x:p - if (host.contains(":")) { - addHost(host.split(":").first(), host.split(":").last().toUInt()); - } else { - // If no port, use default - addHost(host, _localPort); - } + emit connected(); } -void UDPConfiguration::addHost(const QString& host, quint16 port) +void UDPLink::_onDisconnected() { - QString ipAdd = get_ip_address(host); - if (ipAdd.isEmpty()) { - qWarning() << "UDP:" << "Could not resolve host:" << host << "port:" << port; - } else { - QHostAddress address(ipAdd); - if(!contains_target(_targetHosts, address, port)) { - UDPCLient* newTarget = new UDPCLient(address, port); - _targetHosts.append(newTarget); - _updateHostList(); - } - } + emit disconnected(); } -void UDPConfiguration::removeHost(const QString host) +void UDPLink::_onErrorOccurred(const QString &errorString) { - if (host.contains(":")) { - QHostAddress address = QHostAddress(get_ip_address(host.split(":").first())); - quint16 port = host.split(":").last().toUInt(); - for (int i=0; i<_targetHosts.size(); i++) { - UDPCLient* target = _targetHosts.at(i); - if(target->address == address && target->port == port) { - _targetHosts.removeAt(i); - delete target; - _updateHostList(); - return; - } - } - } - qWarning() << "UDP:" << "Could not remove unknown host:" << host; - _updateHostList(); + qCWarning(UDPLinkLog) << "Communication error:" << errorString; + emit communicationError(tr("UDP Link Error"), tr("Link %1: %2").arg(_udpConfig->name(), errorString)); } -void UDPConfiguration::setLocalPort(quint16 port) +void UDPLink::_onDataReceived(const QByteArray &data) { - _localPort = port; + emit bytesReceived(this, data); } -void UDPConfiguration::saveSettings(QSettings& settings, const QString& root) +void UDPLink::_onDataSent(const QByteArray &data) { - settings.beginGroup(root); - settings.setValue("port", (int)_localPort); - settings.setValue("hostCount", _targetHosts.size()); - for (int i=0; i<_targetHosts.size(); i++) { - UDPCLient* target = _targetHosts.at(i); - QString hkey = QString("host%1").arg(i); - settings.setValue(hkey, target->address.toString()); - QString pkey = QString("port%1").arg(i); - settings.setValue(pkey, target->port); - } - settings.endGroup(); + emit bytesSent(this, data); } -void UDPConfiguration::loadSettings(QSettings& settings, const QString& root) +void UDPLink::_writeBytes(const QByteArray& bytes) { - AutoConnectSettings* acSettings = SettingsManager::instance()->autoConnectSettings(); - _clearTargetHosts(); - settings.beginGroup(root); - _localPort = (quint16)settings.value("port", acSettings->udpListenPort()->rawValue().toInt()).toUInt(); - int hostCount = settings.value("hostCount", 0).toInt(); - for (int i=0; iaddress.toString()) + ":" + QString("%1").arg(target->port); - _hostList << host; - } - emit hostListChanged(); + return QGCDeviceInfo::isNetworkWired(); } diff --git a/src/Comms/UDPLink.h b/src/Comms/UDPLink.h index 0c151a09a9e..b35ac1113ce 100644 --- a/src/Comms/UDPLink.h +++ b/src/Comms/UDPLink.h @@ -9,130 +9,180 @@ #pragma once -#include "LinkConfiguration.h" -#include "LinkInterface.h" - -#include +#include #include +#include #include -#include +#include #include -#if defined(QGC_ZEROCONF_ENABLED) +#ifdef QGC_ZEROCONF_ENABLED +#ifdef Q_OS_WIN +#define WIN32_LEAN_AND_MEAN +#endif #include #endif -class LinkManager; +#include "LinkConfiguration.h" +#include "LinkInterface.h" + class QUdpSocket; +class QThread; -class UDPCLient { -public: - UDPCLient(const QHostAddress& address_, quint16 port_) - : address(address_) - , port(port_) +Q_DECLARE_LOGGING_CATEGORY(UDPLinkLog) + +/*===========================================================================*/ + +struct UDPClient +{ + UDPClient(const QHostAddress &address, quint16 port) + : address(address) + , port(port) {} - UDPCLient(const UDPCLient* other) + + explicit UDPClient(const UDPClient *other) : address(other->address) , port(other->port) {} - QHostAddress address; - quint16 port; + + bool operator==(const UDPClient &other) const + { + return ((address == other.address) && (port == other.port)); + } + + UDPClient &operator=(const UDPClient &other) + { + address = other.address; + port = other.port; + + return *this; + } + + QHostAddress address; + quint16 port = 0; }; +/*===========================================================================*/ + class UDPConfiguration : public LinkConfiguration { Q_OBJECT + + Q_PROPERTY(QStringList hostList READ hostList NOTIFY hostListChanged) + Q_PROPERTY(quint16 localPort READ localPort WRITE setLocalPort NOTIFY localPortChanged) + public: + explicit UDPConfiguration(const QString &name, QObject *parent = nullptr); + explicit UDPConfiguration(const UDPConfiguration *source, QObject *parent = nullptr); + virtual ~UDPConfiguration(); + + Q_INVOKABLE void addHost(const QString &host); + Q_INVOKABLE void addHost(const QString &host, quint16 port); + Q_INVOKABLE void removeHost(const QString &host); + Q_INVOKABLE void removeHost(const QString &host, quint16 port); + + LinkType type() const override { return LinkConfiguration::TypeUdp; } + void copyFrom(const LinkConfiguration *source) override; + void loadSettings(QSettings &settings, const QString &root) override; + void saveSettings(QSettings &settings, const QString &root) override; + QString settingsURL() override { return QStringLiteral("UdpSettings.qml"); } + QString settingsTitle() override { return tr("UDP Link Settings"); } + + QStringList hostList() const { return _hostList; } + QList> targetHosts() const { return _targetHosts; } + quint16 localPort() const { return _localPort; } + void setLocalPort(quint16 port) { if (port != _localPort) { _localPort = port; emit localPortChanged(); } } - Q_PROPERTY(quint16 localPort READ localPort WRITE setLocalPort NOTIFY localPortChanged) - Q_PROPERTY(QStringList hostList READ hostList NOTIFY hostListChanged) +signals: + void hostListChanged(); + void localPortChanged(); - UDPConfiguration(const QString& name); - UDPConfiguration(const UDPConfiguration* source); - ~UDPConfiguration(); +private: + void _updateHostList(); - quint16 localPort () const{ return _localPort; } + static QString _getIpAddress(const QString &address); - /// @param[in] host Host name in standard formatt, e.g. localhost:14551 or 192.168.1.1:14551 - Q_INVOKABLE void addHost (const QString host); + QStringList _hostList; + QList> _targetHosts; + quint16 _localPort = 0; +}; - /// @param[in] host Host name, e.g. localhost or 192.168.1.1 - /// @param[in] port Port number - void addHost (const QString& host, quint16 port); +/*===========================================================================*/ - /// @param[in] host Host name, e.g. localhost or 192.168.1.1 - Q_INVOKABLE void removeHost (const QString host); +class UDPWorker : public QObject +{ + Q_OBJECT - void setLocalPort(quint16 port); - QStringList hostList (void) const { return _hostList; } - const QList targetHosts (void) const { return _targetHosts; } +public: + explicit UDPWorker(const UDPConfiguration *config, QObject *parent = nullptr); + virtual ~UDPWorker(); - /// LinkConfiguration overrides - LinkType type (void) const override { return LinkConfiguration::TypeUdp; } - void copyFrom (const LinkConfiguration* source) override; - void loadSettings (QSettings& settings, const QString& root) override; - void saveSettings (QSettings& settings, const QString& root) override; - QString settingsURL (void) override { return "UdpSettings.qml"; } - QString settingsTitle (void) override { return tr("UDP Link Settings"); } + bool isConnected() const; + +public slots: + void setupSocket(); + void connectLink(); + void disconnectLink(); + void writeData(const QByteArray &data); signals: - void localPortChanged (void); - void hostListChanged (void); + void connected(); + void disconnected(); + void errorOccurred(const QString &errorString); + void dataReceived(const QByteArray &data); + void dataSent(const QByteArray &data); + +private slots: + void _onSocketConnected(); + void _onSocketDisconnected(); + void _onSocketReadyRead(); + void _onSocketBytesWritten(qint64 bytes); + void _onSocketErrorOccurred(QAbstractSocket::SocketError socketError); private: - void _updateHostList (void); - void _clearTargetHosts (void); - void _copyFrom (const LinkConfiguration *source); + const UDPConfiguration *_udpConfig = nullptr; + QUdpSocket *_socket = nullptr; + QMutex _sessionTargetsMutex; + QList> _sessionTargets; + bool _isConnected = false; + + static const QHostAddress _multicastGroup; + +#ifdef QGC_ZEROCONF_ENABLED + void _registerZeroconf(uint16_t port); + void _deregisterZeroconf(); + static void _zeroconfRegisterCallback(DNSServiceRef sdRef, DNSServiceFlags flags, DNSServiceErrorType errorCode, const char *name, const char *regtype, const char *domain, void *context); - QList _targetHosts; - QStringList _hostList; - quint16 _localPort; + DNSServiceRef _dnssServiceRef = nullptr; +#endif }; +/*===========================================================================*/ + class UDPLink : public LinkInterface { Q_OBJECT public: - UDPLink(SharedLinkConfigurationPtr& config); + explicit UDPLink(SharedLinkConfigurationPtr &config, QObject *parent = nullptr); virtual ~UDPLink(); - // LinkInterface overrides - bool isConnected (void) const override; - void disconnect (void) override; - bool isSecureConnection (void) override; - - // QThread overrides - void run(void) override; - -public slots: - void readBytes(void); + bool isConnected() const override; + void disconnect() override; + bool isSecureConnection() override; private slots: - // LinkInterface overrides void _writeBytes(const QByteArray &data) override; + void _onConnected(); + void _onDisconnected(); + void _onErrorOccurred(const QString &errorString); + void _onDataReceived(const QByteArray &data); + void _onDataSent(const QByteArray &data); private: + bool _connect() override; - // LinkInterface overrides - bool _connect(void) override; - - bool _isIpLocal (const QHostAddress& add); - bool _hardwareConnect (void); - void _registerZeroconf (uint16_t port, const std::string& regType); - void _deregisterZeroconf(void); - void _writeDataGram (const QByteArray data, const UDPCLient* target); - - bool _running; - QUdpSocket* _socket; - const UDPConfiguration* _udpConfig; - bool _connectState; - QList _sessionTargets; - QMutex _sessionTargetsMutex; - QList _localAddresses; -#if defined(QGC_ZEROCONF_ENABLED) - DNSServiceRef _dnssServiceRef; -#endif - - static constexpr const char* kZeroconfRegistration = "_qgroundcontrol._udp"; + const UDPConfiguration *_udpConfig = nullptr; + UDPWorker *_worker = nullptr; + QThread *_workerThread = nullptr; }; diff --git a/tools/setup/install-dependencies-debian.sh b/tools/setup/install-dependencies-debian.sh index 4400825b066..116798d51da 100755 --- a/tools/setup/install-dependencies-debian.sh +++ b/tools/setup/install-dependencies-debian.sh @@ -112,7 +112,10 @@ DEBIAN_FRONTEND=noninteractive apt-get -y --quiet install \ speech-dispatcher-audio-plugins \ speech-dispatcher-espeak \ speech-dispatcher-espeak-ng \ - speech-dispatcher-flite \ + speech-dispatcher-flite + +# DNS +# DEBIAN_FRONTEND=noninteractive apt-get -y --quiet install libavahi-compat-libdnssd-dev # Additional DEBIAN_FRONTEND=noninteractive apt-get -y --quiet install \