Skip to content
Snippets Groups Projects
UDPLink.cc 15.6 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();
            QHostAddress address;
            for (int i = 0; i < hostAddresses.size(); i++)
            {
                // Exclude all IPv6 addresses
                if (!hostAddresses.at(i).toString().contains(":"))
                {
                    return hostAddresses.at(i).toString();
                }
            }
        }
        return QString("");
    }
    
    
    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";
        }
    
    pixhawk's avatar
    pixhawk committed
    }
    
    UDPLink::~UDPLink()
    {
    
    Lorenz Meier's avatar
    Lorenz Meier committed
        // Tell the thread to exit
    
    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();
    
    }
    
    void UDPLink::addHost(const QString& host)
    {
    
        _udpConfig->addHost(host);
    
    void UDPLink::removeHost(const QString& host)
    
        _udpConfig->removeHost(host);
    
    pixhawk's avatar
    pixhawk committed
    }
    
    
    void UDPLink::_writeBytes(const QByteArray data)
    
        QStringList goneHosts;
        // Send to all connected systems
        QString host;
        int port;
    
        if(_udpConfig->firstHost(host, port)) {
    
            do {
                QHostAddress currentHost(host);
    
                if(_socket->writeDatagram(data, currentHost, (quint16)port) < 0) {
    
                    // This host is gone. Add to list to be removed
    
                    // We should keep track of hosts that were manually added (static) and
                    // hosts that were added because we heard from them (dynamic). Only
                    // dynamic hosts should be removed and even then, after a few tries, not
                    // the first failure. In the mean time, we don't remove anything.
                    if(REMOVE_GONE_HOSTS) {
                        goneHosts.append(host);
                    }
                } 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());
    
            } while (_udpConfig->nextHost(host, port));
    
            //-- Remove hosts that are no longer there
    
            foreach (const QString& ghost, goneHosts) {
    
                _udpConfig->removeHost(ghost);
    
    pixhawk's avatar
    pixhawk committed
    /**
     * @brief Read a number of bytes from the interface.
     **/
    
    pixhawk's avatar
    pixhawk committed
    {
    
    dogmaphobic's avatar
    dogmaphobic committed
        QByteArray databuffer;
    
        {
            QByteArray datagram;
    
            datagram.resize(_socket->pendingDatagramSize());
    
            QHostAddress sender;
            quint16 senderPort;
    
            _socket->readDatagram(datagram.data(), datagram.size(), &sender, &senderPort);
    
    dogmaphobic's avatar
    dogmaphobic committed
            databuffer.append(datagram);
            //-- Wait a bit before sending it over
            if(databuffer.size() > 10 * 1024) {
                emit bytesReceived(this, databuffer);
                databuffer.clear();
            }
    
            _logInputDataRate(datagram.length(), QDateTime::currentMSecsSinceEpoch());
    
            // 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
    
            _udpConfig->addHost(sender.toString(), (int)senderPort);
    
    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->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()) {
            addHost(targetHostIP, settings->udpTargetHostPort()->rawValue().toInt());
        }
    
    UDPConfiguration::UDPConfiguration(UDPConfiguration* source) : LinkConfiguration(source)
    
        _localPort = source->localPort();
        QString host;
        int port;
    
        _hostList.clear();
    
        if(source->firstHost(host, port)) {
            do {
                addHost(host, port);
            } while(source->nextHost(host, port));
        }
    
    void UDPConfiguration::copyFrom(LinkConfiguration *source)
    
        LinkConfiguration::copyFrom(source);
    
        UDPConfiguration* usource = dynamic_cast<UDPConfiguration*>(source);
    
    DonLakeFlyer's avatar
    DonLakeFlyer committed
        if (usource) {
            _localPort = usource->localPort();
            _hosts.clear();
            QString host;
            int port;
            if(usource->firstHost(host, port)) {
                do {
                    addHost(host, port);
                } while(usource->nextHost(host, port));
            }
        } else {
            qWarning() << "Internal error";
    
        }
    }
    
    /**
     * @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
    
            addHost(host.split(":").first(), host.split(":").last().toInt());
    
        // If no port, use default
    
    dogmaphobic's avatar
    dogmaphobic committed
            addHost(host, (int)_localPort);
    
        }
    }
    
    void UDPConfiguration::addHost(const QString& host, int port)
    {
    
    dogmaphobic's avatar
    dogmaphobic committed
        bool changed = false;
    
    dogmaphobic's avatar
    dogmaphobic committed
        QMutexLocker locker(&_confMutex);
        if(_hosts.contains(host)) {
            if(_hosts[host] != port) {
                _hosts[host] = port;
    
    dogmaphobic's avatar
    dogmaphobic committed
                changed = true;
    
    dogmaphobic's avatar
    dogmaphobic committed
            }
        } else {
    
            QString ipAdd = get_ip_address(host);
            if(ipAdd.isEmpty()) {
    
                qWarning() << "UDP:" << "Could not resolve host:" << host << "port:" << port;
    
                // 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.
                bool not_local = true;
                // Run through all IPv4 interfaces and check if their canonical
                // IP address in string representation matches the source IP address
                foreach (const QHostAddress &address, QNetworkInterface::allAddresses()) {
                    if (address.protocol() == QAbstractSocket::IPv4Protocol) {
                        if (ipAdd.endsWith(address.toString())) {
                            // This is a local address of the same host
                            not_local = false;
                        }
                    }
                }
                if (not_local) {
                    // This is a normal remote host, add it using its IPv4 address
                    _hosts[ipAdd] = port;
                    //qDebug() << "UDP:" << "Adding Host:" << ipAdd << ":" << port;
                } else {
                    // It is localhost, so talk to it through the IPv4 loopback interface
                    _hosts["127.0.0.1"] = port;
                }
    
    dogmaphobic's avatar
    dogmaphobic committed
                changed = true;
    
    dogmaphobic's avatar
    dogmaphobic committed
            }
        }
    
    dogmaphobic's avatar
    dogmaphobic committed
        if(changed) {
            _updateHostList();
        }
    
    void UDPConfiguration::removeHost(const QString host)
    
    dogmaphobic's avatar
    dogmaphobic committed
        QMutexLocker locker(&_confMutex);
    
        QString tHost = host;
        if (tHost.contains(":")) {
            tHost = tHost.split(":").first();
        }
        tHost = tHost.trimmed();
        QMap<QString, int>::iterator i = _hosts.find(tHost);
        if(i != _hosts.end()) {
    
            //qDebug() << "UDP:" << "Removed host:" << host;
    
        } else {
            qWarning() << "UDP:" << "Could not remove unknown host:" << host;
    
        _updateHostList();
    
    }
    
    bool UDPConfiguration::firstHost(QString& host, int& port)
    {
    
    dogmaphobic's avatar
    dogmaphobic committed
        _confMutex.lock();
    
        _it = _hosts.begin();
        if(_it == _hosts.end()) {
    
    dogmaphobic's avatar
    dogmaphobic committed
            _confMutex.unlock();
    
    dogmaphobic's avatar
    dogmaphobic committed
        _confMutex.unlock();
    
        return nextHost(host, port);
    }
    
    bool UDPConfiguration::nextHost(QString& host, int& port)
    {
    
    dogmaphobic's avatar
    dogmaphobic committed
        QMutexLocker locker(&_confMutex);
    
        if(_it != _hosts.end()) {
            host = _it.key();
            port = _it.value();
            _it++;
            return true;
        }
        return false;
    }
    
    void UDPConfiguration::setLocalPort(quint16 port)
    {
        _localPort = port;
    }
    
    void UDPConfiguration::saveSettings(QSettings& settings, const QString& root)
    {
        _confMutex.lock();
        settings.beginGroup(root);
        settings.setValue("port", (int)_localPort);
        settings.setValue("hostCount", _hosts.count());
        int index = 0;
        QMap<QString, int>::const_iterator it = _hosts.begin();
        while(it != _hosts.end()) {
            QString hkey = QString("host%1").arg(index);
            settings.setValue(hkey, it.key());
            QString pkey = QString("port%1").arg(index);
            settings.setValue(pkey, it.value());
            it++;
            index++;
        }
        settings.endGroup();
        _confMutex.unlock();
    }
    
    void UDPConfiguration::loadSettings(QSettings& settings, const QString& root)
    {
    
        AutoConnectSettings* acSettings = qgcApp()->toolbox()->settingsManager()->autoConnectSettings();
    
    
    dogmaphobic's avatar
    dogmaphobic committed
        _confMutex.unlock();
        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)) {
                addHost(settings.value(hkey).toString(), settings.value(pkey).toInt());
            }
        }
        settings.endGroup();
    
        _updateHostList();
    
    }
    
    void UDPConfiguration::updateSettings()
    {
        if(_link) {
            UDPLink* ulink = dynamic_cast<UDPLink*>(_link);
            if(ulink) {
                ulink->_restartConnection();
            }
        }
    
    
    void UDPConfiguration::_updateHostList()
    {
        _hostList.clear();
        QMap<QString, int>::const_iterator it = _hosts.begin();
        while(it != _hosts.end()) {
            QString host = QString("%1").arg(it.key()) + ":" + QString("%1").arg(it.value());
            _hostList += host;
            it++;
        }
        emit hostListChanged();
    }