Skip to content
Snippets Groups Projects
UDPLink.cc 15.3 KiB
Newer Older
  • Learn to ignore specific revisions
  • /****************************************************************************
     *
     *   (c) 2009-2016 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
     *
     * QGroundControl is licensed according to the terms in the file
     * COPYING.md in the root of the source code directory.
     *
     ****************************************************************************/
    
    pixhawk's avatar
    pixhawk committed
    
    
    /**
     * @file
     *   @brief Definition of UDP connection (server) for unmanned vehicles
     *   @author Lorenz Meier <mavteam@student.ethz.ch>
     *
     */
    
    
    #include <QtGlobal>
    
    pixhawk's avatar
    pixhawk committed
    #include <QTimer>
    #include <QList>
    #include <QDebug>
    #include <QMutexLocker>
    
    #include <QNetworkProxy>
    
    pixhawk's avatar
    pixhawk committed
    #include <iostream>
    
    pixhawk's avatar
    pixhawk committed
    #include "UDPLink.h"
    
    #include "QGCApplication.h"
    #include "SettingsManager.h"
    #include "AutoConnectSettings.h"
    
    pixhawk's avatar
    pixhawk committed
    
    
    #define REMOVE_GONE_HOSTS 0
    
    
    static const char* kZeroconfRegistration = "_qgroundcontrol._udp";
    
    
    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())) {
    
            return false;
    
        } else {
            return true;
        }
    
    }
    
    static QString get_ip_address(const QString& address)
    {
        if(is_ip(address))
            return address;
        // Need to look it up
        QHostInfo info = QHostInfo::fromName(address);
        if (info.error() == QHostInfo::NoError)
        {
            QList<QHostAddress> hostAddresses = info.addresses();
            for (int i = 0; i < hostAddresses.size(); i++)
            {
                // Exclude all IPv6 addresses
                if (!hostAddresses.at(i).toString().contains(":"))
                {
                    return hostAddresses.at(i).toString();
                }
            }
        }
    
    static bool contains_target(const QList<UDPCLient*> list, const QHostAddress& address, quint16 port)
    
    Gus Grubba's avatar
    Gus Grubba committed
    {
    
        for(UDPCLient* target: list) {
    
    Gus Grubba's avatar
    Gus Grubba committed
            if(target->address == address && target->port == port) {
                return true;
            }
        }
        return false;
    }
    
    
    UDPLink::UDPLink(SharedLinkConfigurationPointer& config)
        : LinkInterface(config)
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
        #if defined(QGC_ZEROCONF_ENABLED)
    
        , _dnssServiceRef(NULL)
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
        #endif
    
        , _socket(NULL)
        , _udpConfig(qobject_cast<UDPConfiguration*>(config.data()))
        , _connectState(false)
    
    pixhawk's avatar
    pixhawk committed
    {
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
        if (!_udpConfig) {
            qWarning() << "Internal error";
        }
    
        for (const QHostAddress &address: QNetworkInterface::allAddresses()) {
    
    pixhawk's avatar
    pixhawk committed
    }
    
    UDPLink::~UDPLink()
    {
    
    Lorenz Meier's avatar
    Lorenz Meier committed
        // Tell the thread to exit
    
    Gus Grubba's avatar
    Gus Grubba committed
        // Clear client list
    
        qDeleteAll(_sessionTargets);
        _sessionTargets.clear();
    
    Lorenz Meier's avatar
    Lorenz Meier committed
        // Wait for it to exit
        wait();
    
    pixhawk's avatar
    pixhawk committed
    }
    
    /**
     * @brief Runs the thread
     *
     **/
    void UDPLink::run()
    {
    
            _deregisterZeroconf();
    
            _socket->close();
        }
    
    pixhawk's avatar
    pixhawk committed
    }
    
    
    pixhawk's avatar
    pixhawk committed
    {
    
    pixhawk's avatar
    pixhawk committed
    }
    
    
    pixhawk's avatar
    pixhawk committed
    {
    
        return _udpConfig->name();
    
    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 (const QHostAddress &address: _localAddress) {
    
            if (address == add) {
                // This is a local address of the same host
                return true;
            }
        }
        return false;
    }
    
    
    void UDPLink::_writeBytes(const QByteArray data)
    
    Gus Grubba's avatar
    Gus Grubba committed
        // Send to all manually targeted systems
    
        for(UDPCLient* target: _udpConfig->targetHosts()) {
    
    Gus Grubba's avatar
    Gus Grubba committed
            // Skip it if it's part of the session clients below
    
            if(!contains_target(_sessionTargets, target->address, target->port)) {
    
    Gus Grubba's avatar
    Gus Grubba committed
                _writeDataGram(data, target);
    
    Gus Grubba's avatar
    Gus Grubba committed
        // Send to all connected systems
    
        for(UDPCLient* target: _sessionTargets) {
    
    Gus Grubba's avatar
    Gus Grubba committed
            _writeDataGram(data, target);
        }
    }
    
    void UDPLink::_writeDataGram(const QByteArray data, const UDPCLient* target)
    {
    
        //qDebug() << "UDP Out" << target->address << target->port;
    
    Gus Grubba's avatar
    Gus Grubba committed
        if(_socket->writeDatagram(data, target->address, target->port) < 0) {
            qWarning() << "Error writing to" << target->address << target->port;
        } else {
            // Only log rate if data actually got sent. Not sure about this as
            // "host not there" takes time too regardless of size of data. In fact,
            // 1 byte or "UDP frame size" bytes are the same as that's the data
            // unit sent by UDP.
            _logOutputDataRate(data.size(), QDateTime::currentMSecsSinceEpoch());
        }
    
    pixhawk's avatar
    pixhawk committed
    /**
     * @brief Read a number of bytes from the interface.
     **/
    
    pixhawk's avatar
    pixhawk committed
    {
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
            return;
        }
        QByteArray databuffer;
        while (_socket->hasPendingDatagrams())
        {
            QByteArray datagram;
            datagram.resize(_socket->pendingDatagramSize());
            QHostAddress sender;
            quint16 senderPort;
    
            //-- Note: This call is broken in Qt 5.9.3 on Windows. It always returns a blank sender and 0 for the port.
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
            _socket->readDatagram(datagram.data(), datagram.size(), &sender, &senderPort);
            databuffer.append(datagram);
            //-- Wait a bit before sending it over
            if(databuffer.size() > 10 * 1024) {
    
    dogmaphobic's avatar
    dogmaphobic committed
                emit bytesReceived(this, databuffer);
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
                databuffer.clear();
    
            _logInputDataRate(datagram.length(), QDateTime::currentMSecsSinceEpoch());
    
    Gus Grubba's avatar
    Gus Grubba committed
            // 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
    
    Gus Grubba's avatar
    Gus Grubba committed
            QHostAddress asender = sender;
    
    Gus Grubba's avatar
    Gus Grubba committed
                asender = QHostAddress(QString("127.0.0.1"));
            }
    
            if(!contains_target(_sessionTargets, asender, senderPort)) {
    
    Gus Grubba's avatar
    Gus Grubba committed
                qDebug() << "Adding target" << asender << senderPort;
                UDPCLient* target = new UDPCLient(asender, senderPort);
                _sessionTargets.append(target);
            }
    
    pixhawk's avatar
    pixhawk committed
        }
    
    dogmaphobic's avatar
    dogmaphobic committed
        //-- Send whatever is left
        if(databuffer.size()) {
            emit bytesReceived(this, databuffer);
        }
    
    pixhawk's avatar
    pixhawk committed
    }
    
    /**
     * @brief Disconnect the connection.
     *
     * @return True if connection has been disconnected, false if connection couldn't be disconnected.
     **/
    
    Don Gagne's avatar
    Don Gagne committed
    void UDPLink::_disconnect(void)
    
    pixhawk's avatar
    pixhawk committed
    {
    
        if (_socket) {
            // Make sure delete happen on correct thread
            _socket->deleteLater();
            _socket = NULL;
    
            emit disconnected();
    
    pixhawk's avatar
    pixhawk committed
    }
    
    /**
     * @brief Connect the connection.
     *
     * @return True if connection has been established, false if connection couldn't be established.
     **/
    
    bool UDPLink::_connect(void)
    
    pixhawk's avatar
    pixhawk committed
    {
    
        if(this->isRunning() || _running)
    
        start(NormalPriority);
    
    oberion's avatar
    oberion committed
    }
    
    
    oberion's avatar
    oberion committed
    {
    
        if (_socket) {
            delete _socket;
            _socket = NULL;
        }
    
        QHostAddress host = QHostAddress::AnyIPv4;
    
        _socket = new QUdpSocket(this);
    
        _socket->setProxy(QNetworkProxy::NoProxy);
    
        _connectState = _socket->bind(host, _udpConfig->localPort(), QAbstractSocket::ReuseAddressHint | QUdpSocket::ShareAddress);
    
            _socket->joinMulticastGroup(QHostAddress("224.0.0.1"));
    
    dogmaphobic's avatar
    dogmaphobic committed
            //-- Make sure we have a large enough IO buffers
    
    Don Gagne's avatar
    Don Gagne committed
    #ifdef __mobile__
    
    dogmaphobic's avatar
    dogmaphobic committed
            _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);
    
        } else {
    
    Don Gagne's avatar
    Don Gagne committed
            emit communicationError(tr("UDP Link Error"), tr("Error binding UDP port: %1").arg(_socket->errorString()));
    
    pixhawk's avatar
    pixhawk committed
    }
    
    /**
     * @brief Check if connection is active.
     *
     * @return True if link is connected, false otherwise.
     **/
    
    bool UDPLink::isConnected() const
    
    pixhawk's avatar
    pixhawk committed
    }
    
    
    qint64 UDPLink::getConnectionSpeed() const
    
    pixhawk's avatar
    pixhawk committed
    {
    
        return 54000000; // 54 Mbit
    }
    
    qint64 UDPLink::getCurrentInDataRate() const
    {
        return 0;
    
    pixhawk's avatar
    pixhawk committed
    }
    
    
    qint64 UDPLink::getCurrentOutDataRate() const
    
    pixhawk's avatar
    pixhawk committed
    {
    
    pixhawk's avatar
    pixhawk committed
    }
    
    
    void UDPLink::_registerZeroconf(uint16_t port, const std::string &regType)
    {
    #if defined(QGC_ZEROCONF_ENABLED)
        DNSServiceErrorType result = DNSServiceRegister(&_dnssServiceRef, 0, 0, 0,
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
                                                        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"));
    
            _dnssServiceRef = NULL;
        }
    #else
        Q_UNUSED(port);
        Q_UNUSED(regType);
    #endif
    }
    
    void UDPLink::_deregisterZeroconf()
    {
    #if defined(QGC_ZEROCONF_ENABLED)
        if (_dnssServiceRef)
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
        {
            DNSServiceRefDeallocate(_dnssServiceRef);
            _dnssServiceRef = NULL;
        }
    
    //--------------------------------------------------------------------------
    //-- UDPConfiguration
    
    UDPConfiguration::UDPConfiguration(const QString& name) : LinkConfiguration(name)
    
        AutoConnectSettings* settings = qgcApp()->toolbox()->settingsManager()->autoConnectSettings();
        _localPort = settings->udpListenPort()->rawValue().toInt();
        QString targetHostIP = settings->udpTargetHostIP()->rawValue().toString();
        if (!targetHostIP.isEmpty()) {
    
    Gus Grubba's avatar
    Gus Grubba committed
            addHost(targetHostIP, settings->udpTargetHostPort()->rawValue().toUInt());
    
    UDPConfiguration::UDPConfiguration(UDPConfiguration* source) : LinkConfiguration(source)
    
    Gus Grubba's avatar
    Gus Grubba committed
        _copyFrom(source);
    }
    
    UDPConfiguration::~UDPConfiguration()
    {
        _clearTargetHosts();
    
    void UDPConfiguration::copyFrom(LinkConfiguration *source)
    
        LinkConfiguration::copyFrom(source);
    
    Gus Grubba's avatar
    Gus Grubba committed
        _copyFrom(source);
    }
    
    void UDPConfiguration::_copyFrom(LinkConfiguration *source)
    {
    
        UDPConfiguration* usource = dynamic_cast<UDPConfiguration*>(source);
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
        if (usource) {
            _localPort = usource->localPort();
    
    Gus Grubba's avatar
    Gus Grubba committed
            _clearTargetHosts();
    
            for(UDPCLient* target: usource->targetHosts()) {
    
                if(!contains_target(_targetHosts, target->address, target->port)) {
    
    Gus Grubba's avatar
    Gus Grubba committed
                    UDPCLient* newTarget = new UDPCLient(target);
                    _targetHosts.append(newTarget);
    
                    _updateHostList();
    
    Gus Grubba's avatar
    Gus Grubba committed
                }
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
            }
        } else {
            qWarning() << "Internal error";
    
    Gus Grubba's avatar
    Gus Grubba committed
    void UDPConfiguration::_clearTargetHosts()
    {
    
        qDeleteAll(_targetHosts);
        _targetHosts.clear();
    
    Gus Grubba's avatar
    Gus Grubba committed
    }
    
    
    /**
     * @param host Hostname in standard formatt, e.g. localhost:14551 or 192.168.1.1:14551
     */
    
    void UDPConfiguration::addHost(const QString host)
    
        // Handle x.x.x.x:p
    
    Gus Grubba's avatar
    Gus Grubba committed
            addHost(host.split(":").first(), host.split(":").last().toUInt());
    
        // If no port, use default
    
    Gus Grubba's avatar
    Gus Grubba committed
            addHost(host, _localPort);
    
    Gus Grubba's avatar
    Gus Grubba committed
    void UDPConfiguration::addHost(const QString& host, quint16 port)
    
    Gus Grubba's avatar
    Gus Grubba committed
        QString ipAdd = get_ip_address(host);
        if(ipAdd.isEmpty()) {
            qWarning() << "UDP:" << "Could not resolve host:" << host << "port:" << port;
    
    dogmaphobic's avatar
    dogmaphobic committed
        } else {
    
    Gus Grubba's avatar
    Gus Grubba committed
            QHostAddress address(ipAdd);
    
            if(!contains_target(_targetHosts, address, port)) {
    
    Gus Grubba's avatar
    Gus Grubba committed
                UDPCLient* newTarget = new UDPCLient(address, port);
                _targetHosts.append(newTarget);
                _updateHostList();
    
    dogmaphobic's avatar
    dogmaphobic committed
            }
        }
    
    void UDPConfiguration::removeHost(const QString host)
    
    Gus Grubba's avatar
    Gus Grubba committed
        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;
                }
            }
    
    Gus Grubba's avatar
    Gus Grubba committed
        qWarning() << "UDP:" << "Could not remove unknown host:" << host;
    
        _updateHostList();
    
    }
    
    void UDPConfiguration::setLocalPort(quint16 port)
    {
        _localPort = port;
    }
    
    void UDPConfiguration::saveSettings(QSettings& settings, const QString& root)
    {
        settings.beginGroup(root);
        settings.setValue("port", (int)_localPort);
    
    Gus Grubba's avatar
    Gus Grubba committed
        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();
    }
    
    void UDPConfiguration::loadSettings(QSettings& settings, const QString& root)
    {
    
        AutoConnectSettings* acSettings = qgcApp()->toolbox()->settingsManager()->autoConnectSettings();
    
    Gus Grubba's avatar
    Gus Grubba committed
        _clearTargetHosts();
    
    dogmaphobic's avatar
    dogmaphobic committed
        settings.beginGroup(root);
    
        _localPort = (quint16)settings.value("port", acSettings->udpListenPort()->rawValue().toInt()).toUInt();
    
        int hostCount = settings.value("hostCount", 0).toInt();
        for(int i = 0; i < hostCount; i++) {
            QString hkey = QString("host%1").arg(i);
            QString pkey = QString("port%1").arg(i);
            if(settings.contains(hkey) && settings.contains(pkey)) {
    
    Gus Grubba's avatar
    Gus Grubba committed
                addHost(settings.value(hkey).toString(), settings.value(pkey).toUInt());
    
        _updateHostList();
    
    }
    
    void UDPConfiguration::updateSettings()
    {
        if(_link) {
            UDPLink* ulink = dynamic_cast<UDPLink*>(_link);
            if(ulink) {
                ulink->_restartConnection();
            }
        }
    
    
    void UDPConfiguration::_updateHostList()
    {
        _hostList.clear();
    
    Gus Grubba's avatar
    Gus Grubba committed
        for(int i = 0; i < _targetHosts.size(); i++) {
            UDPCLient* target = _targetHosts.at(i);
            QString host = QString("%1").arg(target->address.toString()) + ":" + QString("%1").arg(target->port);
            _hostList << host;
    
        }
        emit hostListChanged();
    }