UDPLink.cc 15.1 KB
Newer Older
1 2 3 4 5 6 7 8
/****************************************************************************
 *
 *   (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
9 10 11 12 13 14 15 16 17


/**
 * @file
 *   @brief Definition of UDP connection (server) for unmanned vehicles
 *   @author Lorenz Meier <mavteam@student.ethz.ch>
 *
 */

18
#include <QtGlobal>
pixhawk's avatar
pixhawk committed
19 20 21 22
#include <QTimer>
#include <QList>
#include <QDebug>
#include <QMutexLocker>
23
#include <QNetworkProxy>
24
#include <QNetworkInterface>
pixhawk's avatar
pixhawk committed
25
#include <iostream>
26

pixhawk's avatar
pixhawk committed
27
#include "UDPLink.h"
28
#include "QGC.h"
29
#include <QHostInfo>
pixhawk's avatar
pixhawk committed
30

31 32
#define REMOVE_GONE_HOSTS 0

33 34
static const char* kZeroconfRegistration = "_qgroundcontrol._udp";

35 36 37
static bool is_ip(const QString& address)
{
    int a,b,c,d;
38 39
    if (sscanf(address.toStdString().c_str(), "%d.%d.%d.%d", &a, &b, &c, &d) != 4
            && strcmp("::1", address.toStdString().c_str())) {
40
        return false;
41 42 43
    } else {
        return true;
    }
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
}

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("");
}

68 69
UDPLink::UDPLink(SharedLinkConfigurationPointer& config)
    : LinkInterface(config)
DonLakeFlyer's avatar
DonLakeFlyer committed
70
    #if defined(QGC_ZEROCONF_ENABLED)
71
    , _dnssServiceRef(NULL)
DonLakeFlyer's avatar
DonLakeFlyer committed
72
    #endif
73
    , _running(false)
74 75 76
    , _socket(NULL)
    , _udpConfig(qobject_cast<UDPConfiguration*>(config.data()))
    , _connectState(false)
pixhawk's avatar
pixhawk committed
77
{
DonLakeFlyer's avatar
DonLakeFlyer committed
78 79 80
    if (!_udpConfig) {
        qWarning() << "Internal error";
    }
81
    moveToThread(this);
pixhawk's avatar
pixhawk committed
82 83 84 85
}

UDPLink::~UDPLink()
{
86
    _disconnect();
Lorenz Meier's avatar
Lorenz Meier committed
87
    // Tell the thread to exit
88
    _running = false;
89
    quit();
Lorenz Meier's avatar
Lorenz Meier committed
90 91
    // Wait for it to exit
    wait();
92
    this->deleteLater();
pixhawk's avatar
pixhawk committed
93 94 95 96 97 98 99 100
}

/**
 * @brief Runs the thread
 *
 **/
void UDPLink::run()
{
101
    if(_hardwareConnect()) {
102
        exec();
103
    }
104
    if (_socket) {
105
        _deregisterZeroconf();
106 107
        _socket->close();
    }
pixhawk's avatar
pixhawk committed
108 109
}

110
void UDPLink::_restartConnection()
pixhawk's avatar
pixhawk committed
111
{
112 113 114 115 116
    if(this->isConnected())
    {
        _disconnect();
        _connect();
    }
pixhawk's avatar
pixhawk committed
117 118
}

119
QString UDPLink::getName() const
pixhawk's avatar
pixhawk committed
120
{
121
    return _udpConfig->name();
122 123 124 125
}

void UDPLink::addHost(const QString& host)
{
126
    _udpConfig->addHost(host);
127 128
}

129
void UDPLink::removeHost(const QString& host)
130
{
131
    _udpConfig->removeHost(host);
pixhawk's avatar
pixhawk committed
132 133
}

134
void UDPLink::_writeBytes(const QByteArray data)
135
{
136 137 138
    if (!_socket)
        return;

139 140 141 142
    QStringList goneHosts;
    // Send to all connected systems
    QString host;
    int port;
143
    if(_udpConfig->firstHost(host, port)) {
144 145
        do {
            QHostAddress currentHost(host);
146
            if(_socket->writeDatagram(data, currentHost, (quint16)port) < 0) {
147
                // This host is gone. Add to list to be removed
148 149 150 151 152 153 154 155 156 157 158 159
                // 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.
160
                _logOutputDataRate(data.size(), QDateTime::currentMSecsSinceEpoch());
161
            }
162
        } while (_udpConfig->nextHost(host, port));
163
        //-- Remove hosts that are no longer there
164
        foreach (const QString& ghost, goneHosts) {
165
            _udpConfig->removeHost(ghost);
166 167 168 169
        }
    }
}

pixhawk's avatar
pixhawk committed
170 171 172
/**
 * @brief Read a number of bytes from the interface.
 **/
173
void UDPLink::readBytes()
pixhawk's avatar
pixhawk committed
174
{
dogmaphobic's avatar
dogmaphobic committed
175
    QByteArray databuffer;
176
    while (_socket->hasPendingDatagrams())
177 178
    {
        QByteArray datagram;
179
        datagram.resize(_socket->pendingDatagramSize());
180 181
        QHostAddress sender;
        quint16 senderPort;
182
        _socket->readDatagram(datagram.data(), datagram.size(), &sender, &senderPort);
dogmaphobic's avatar
dogmaphobic committed
183 184 185 186 187 188
        databuffer.append(datagram);
        //-- Wait a bit before sending it over
        if(databuffer.size() > 10 * 1024) {
            emit bytesReceived(this, databuffer);
            databuffer.clear();
        }
189
        _logInputDataRate(datagram.length(), QDateTime::currentMSecsSinceEpoch());
190 191 192 193
        // 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
194
        _udpConfig->addHost(sender.toString(), (int)senderPort);
pixhawk's avatar
pixhawk committed
195
    }
dogmaphobic's avatar
dogmaphobic committed
196 197 198 199
    //-- Send whatever is left
    if(databuffer.size()) {
        emit bytesReceived(this, databuffer);
    }
pixhawk's avatar
pixhawk committed
200 201 202 203 204 205 206
}

/**
 * @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
207
void UDPLink::_disconnect(void)
pixhawk's avatar
pixhawk committed
208
{
209
    _running = false;
210
    quit();
211
    wait();
212 213 214 215
    if (_socket) {
        // Make sure delete happen on correct thread
        _socket->deleteLater();
        _socket = NULL;
216
        emit disconnected();
217 218
    }
    _connectState = false;
pixhawk's avatar
pixhawk committed
219 220 221 222 223 224 225
}

/**
 * @brief Connect the connection.
 *
 * @return True if connection has been established, false if connection couldn't be established.
 **/
226
bool UDPLink::_connect(void)
pixhawk's avatar
pixhawk committed
227
{
228
    if(this->isRunning() || _running)
229
    {
230
        _running = false;
231
        quit();
232
        wait();
233
    }
234
    _running = true;
235
    start(NormalPriority);
236
    return true;
oberion's avatar
oberion committed
237 238
}

239
bool UDPLink::_hardwareConnect()
oberion's avatar
oberion committed
240
{
241 242 243 244
    if (_socket) {
        delete _socket;
        _socket = NULL;
    }
245
    QHostAddress host = QHostAddress::AnyIPv4;
246
    _socket = new QUdpSocket();
247
    _socket->setProxy(QNetworkProxy::NoProxy);
248
    _connectState = _socket->bind(host, _udpConfig->localPort(), QAbstractSocket::ReuseAddressHint | QUdpSocket::ShareAddress);
249
    if (_connectState) {
250
        _socket->joinMulticastGroup(QHostAddress("224.0.0.1"));
dogmaphobic's avatar
dogmaphobic committed
251
        //-- Make sure we have a large enough IO buffers
Don Gagne's avatar
Don Gagne committed
252
#ifdef __mobile__
dogmaphobic's avatar
dogmaphobic committed
253 254 255 256 257 258
        _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
259
        _registerZeroconf(_udpConfig->localPort(), kZeroconfRegistration);
260
        QObject::connect(_socket, &QUdpSocket::readyRead, this, &UDPLink::readBytes);
261
        emit connected();
262
    } else {
Don Gagne's avatar
Don Gagne committed
263
        emit communicationError(tr("UDP Link Error"), tr("Error binding UDP port: %1").arg(_socket->errorString()));
264
    }
265
    return _connectState;
pixhawk's avatar
pixhawk committed
266 267 268 269 270 271 272
}

/**
 * @brief Check if connection is active.
 *
 * @return True if link is connected, false otherwise.
 **/
273
bool UDPLink::isConnected() const
274
{
275
    return _connectState;
pixhawk's avatar
pixhawk committed
276 277
}

278
qint64 UDPLink::getConnectionSpeed() const
pixhawk's avatar
pixhawk committed
279
{
280 281 282 283 284 285
    return 54000000; // 54 Mbit
}

qint64 UDPLink::getCurrentInDataRate() const
{
    return 0;
pixhawk's avatar
pixhawk committed
286 287
}

288
qint64 UDPLink::getCurrentOutDataRate() const
pixhawk's avatar
pixhawk committed
289
{
290
    return 0;
pixhawk's avatar
pixhawk committed
291 292
}

293 294 295 296
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
297 298 299 300 301 302 303 304
                                                    regType.c_str(),
                                                    NULL,
                                                    NULL,
                                                    htons(port),
                                                    0,
                                                    NULL,
                                                    NULL,
                                                    NULL);
305 306 307 308 309 310 311 312 313 314 315 316 317 318 319
    if (result != kDNSServiceErr_NoError)
    {
        emit communicationError("UDP Link Error", "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
320 321 322 323
    {
        DNSServiceRefDeallocate(_dnssServiceRef);
        _dnssServiceRef = NULL;
    }
324 325 326
#endif
}

327 328
//--------------------------------------------------------------------------
//-- UDPConfiguration
329

330
UDPConfiguration::UDPConfiguration(const QString& name) : LinkConfiguration(name)
331
{
332
    _localPort = QGC_UDP_LOCAL_PORT;
333 334
}

335
UDPConfiguration::UDPConfiguration(UDPConfiguration* source) : LinkConfiguration(source)
336
{
337 338 339
    _localPort = source->localPort();
    QString host;
    int port;
340
    _hostList.clear();
341 342 343 344 345
    if(source->firstHost(host, port)) {
        do {
            addHost(host, port);
        } while(source->nextHost(host, port));
    }
346 347
}

348
void UDPConfiguration::copyFrom(LinkConfiguration *source)
349
{
350
    LinkConfiguration::copyFrom(source);
351
    UDPConfiguration* usource = dynamic_cast<UDPConfiguration*>(source);
DonLakeFlyer's avatar
DonLakeFlyer committed
352 353 354 355 356 357 358 359 360 361 362 363
    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";
364 365 366 367 368 369
    }
}

/**
 * @param host Hostname in standard formatt, e.g. localhost:14551 or 192.168.1.1:14551
 */
370
void UDPConfiguration::addHost(const QString host)
371
{
372
    // Handle x.x.x.x:p
373 374
    if (host.contains(":"))
    {
375
        addHost(host.split(":").first(), host.split(":").last().toInt());
376
    }
377
    // If no port, use default
378 379
    else
    {
dogmaphobic's avatar
dogmaphobic committed
380
        addHost(host, (int)_localPort);
381 382 383 384 385
    }
}

void UDPConfiguration::addHost(const QString& host, int port)
{
dogmaphobic's avatar
dogmaphobic committed
386
    bool changed = false;
dogmaphobic's avatar
dogmaphobic committed
387 388 389 390
    QMutexLocker locker(&_confMutex);
    if(_hosts.contains(host)) {
        if(_hosts[host] != port) {
            _hosts[host] = port;
dogmaphobic's avatar
dogmaphobic committed
391
            changed = true;
dogmaphobic's avatar
dogmaphobic committed
392 393
        }
    } else {
394 395
        QString ipAdd = get_ip_address(host);
        if(ipAdd.isEmpty()) {
396
            qWarning() << "UDP:" << "Could not resolve host:" << host << "port:" << port;
397
        } else {
398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424
            // 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
425
            changed = true;
dogmaphobic's avatar
dogmaphobic committed
426 427
        }
    }
dogmaphobic's avatar
dogmaphobic committed
428 429 430
    if(changed) {
        _updateHostList();
    }
431 432
}

433
void UDPConfiguration::removeHost(const QString host)
434
{
dogmaphobic's avatar
dogmaphobic committed
435
    QMutexLocker locker(&_confMutex);
436 437 438 439 440 441 442
    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()) {
443
        //qDebug() << "UDP:" << "Removed host:" << host;
444
        _hosts.erase(i);
445 446
    } else {
        qWarning() << "UDP:" << "Could not remove unknown host:" << host;
447
    }
448
    _updateHostList();
449 450 451 452
}

bool UDPConfiguration::firstHost(QString& host, int& port)
{
dogmaphobic's avatar
dogmaphobic committed
453
    _confMutex.lock();
454 455
    _it = _hosts.begin();
    if(_it == _hosts.end()) {
dogmaphobic's avatar
dogmaphobic committed
456
        _confMutex.unlock();
457 458
        return false;
    }
dogmaphobic's avatar
dogmaphobic committed
459
    _confMutex.unlock();
460 461 462 463 464
    return nextHost(host, port);
}

bool UDPConfiguration::nextHost(QString& host, int& port)
{
dogmaphobic's avatar
dogmaphobic committed
465
    QMutexLocker locker(&_confMutex);
466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503
    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)
{
    _confMutex.lock();
    _hosts.clear();
dogmaphobic's avatar
dogmaphobic committed
504 505
    _confMutex.unlock();
    settings.beginGroup(root);
506
    _localPort = (quint16)settings.value("port", QGC_UDP_LOCAL_PORT).toUInt();
507 508 509 510 511 512 513 514 515
    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();
516
    _updateHostList();
517 518 519 520 521 522 523 524 525 526
}

void UDPConfiguration::updateSettings()
{
    if(_link) {
        UDPLink* ulink = dynamic_cast<UDPLink*>(_link);
        if(ulink) {
            ulink->_restartConnection();
        }
    }
527
}
528 529 530 531 532 533 534 535 536 537 538 539

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();
}