UDPLink.cc 14.5 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 70
UDPLink::UDPLink(SharedLinkConfigurationPointer& config)
    : LinkInterface(config)
#if defined(QGC_ZEROCONF_ENABLED)
71
    , _dnssServiceRef(NULL)
72
#endif
73
    , _running(false)
74 75 76
    , _socket(NULL)
    , _udpConfig(qobject_cast<UDPConfiguration*>(config.data()))
    , _connectState(false)
pixhawk's avatar
pixhawk committed
77
{
78
    Q_ASSERT(_udpConfig);
79
    moveToThread(this);
pixhawk's avatar
pixhawk committed
80 81 82 83
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

285
qint64 UDPLink::getCurrentOutDataRate() const
pixhawk's avatar
pixhawk committed
286
{
287
    return 0;
pixhawk's avatar
pixhawk committed
288 289
}

290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323
void UDPLink::_registerZeroconf(uint16_t port, const std::string &regType)
{
#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("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)
     {
         DNSServiceRefDeallocate(_dnssServiceRef);
         _dnssServiceRef = NULL;
     }
#endif
}

324 325
//--------------------------------------------------------------------------
//-- UDPConfiguration
326

327
UDPConfiguration::UDPConfiguration(const QString& name) : LinkConfiguration(name)
328
{
329
    _localPort = QGC_UDP_LOCAL_PORT;
330 331
}

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

345
void UDPConfiguration::copyFrom(LinkConfiguration *source)
346
{
347
    LinkConfiguration::copyFrom(source);
348 349 350
    UDPConfiguration* usource = dynamic_cast<UDPConfiguration*>(source);
    Q_ASSERT(usource != NULL);
    _localPort = usource->localPort();
351
    _hosts.clear();
352 353 354 355 356 357 358 359 360 361 362 363
    QString host;
    int port;
    if(usource->firstHost(host, port)) {
        do {
            addHost(host, port);
        } while(usource->nextHost(host, port));
    }
}

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

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

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

bool UDPConfiguration::firstHost(QString& host, int& port)
{
dogmaphobic's avatar
dogmaphobic committed
447
    _confMutex.lock();
448 449
    _it = _hosts.begin();
    if(_it == _hosts.end()) {
dogmaphobic's avatar
dogmaphobic committed
450
        _confMutex.unlock();
451 452
        return false;
    }
dogmaphobic's avatar
dogmaphobic committed
453
    _confMutex.unlock();
454 455 456 457 458
    return nextHost(host, port);
}

bool UDPConfiguration::nextHost(QString& host, int& port)
{
dogmaphobic's avatar
dogmaphobic committed
459
    QMutexLocker locker(&_confMutex);
460 461 462 463 464 465 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
    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
498 499
    _confMutex.unlock();
    settings.beginGroup(root);
500
    _localPort = (quint16)settings.value("port", QGC_UDP_LOCAL_PORT).toUInt();
501 502 503 504 505 506 507 508 509
    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();
510
    _updateHostList();
511 512 513 514 515 516 517 518 519 520
}

void UDPConfiguration::updateSettings()
{
    if(_link) {
        UDPLink* ulink = dynamic_cast<UDPLink*>(_link);
        if(ulink) {
            ulink->_restartConnection();
        }
    }
521
}
522 523 524 525 526 527 528 529 530 531 532 533

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