UDPLink.cc 14.8 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
#include <QHostInfo>
27

pixhawk's avatar
pixhawk committed
28
#include "UDPLink.h"
29
#include "QGC.h"
30 31 32
#include "QGCApplication.h"
#include "SettingsManager.h"
#include "AutoConnectSettings.h"
pixhawk's avatar
pixhawk committed
33

34 35
#define REMOVE_GONE_HOSTS 0

36 37
static const char* kZeroconfRegistration = "_qgroundcontrol._udp";

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

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

Gus Grubba's avatar
Gus Grubba committed
71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
static bool is_ip_local(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
    foreach (const QHostAddress &address, QNetworkInterface::allAddresses()) {
        if (address == add) {
            // This is a local address of the same host
            return true;
        }
    }
    return false;
}

92
static bool contains_target(const QList<UDPCLient*> list, const QHostAddress& address, quint16 port)
Gus Grubba's avatar
Gus Grubba committed
93 94 95 96 97 98 99 100 101
{
    foreach(UDPCLient* target, list) {
        if(target->address == address && target->port == port) {
            return true;
        }
    }
    return false;
}

102 103
UDPLink::UDPLink(SharedLinkConfigurationPointer& config)
    : LinkInterface(config)
DonLakeFlyer's avatar
DonLakeFlyer committed
104
    #if defined(QGC_ZEROCONF_ENABLED)
105
    , _dnssServiceRef(NULL)
DonLakeFlyer's avatar
DonLakeFlyer committed
106
    #endif
107
    , _running(false)
108 109 110
    , _socket(NULL)
    , _udpConfig(qobject_cast<UDPConfiguration*>(config.data()))
    , _connectState(false)
pixhawk's avatar
pixhawk committed
111
{
DonLakeFlyer's avatar
DonLakeFlyer committed
112 113 114
    if (!_udpConfig) {
        qWarning() << "Internal error";
    }
115
    moveToThread(this);
pixhawk's avatar
pixhawk committed
116 117 118 119
}

UDPLink::~UDPLink()
{
120
    _disconnect();
Lorenz Meier's avatar
Lorenz Meier committed
121
    // Tell the thread to exit
122
    _running = false;
Gus Grubba's avatar
Gus Grubba committed
123
    // Clear client list
124 125
    qDeleteAll(_sessionTargets);
    _sessionTargets.clear();
126
    quit();
Lorenz Meier's avatar
Lorenz Meier committed
127 128
    // Wait for it to exit
    wait();
129
    this->deleteLater();
pixhawk's avatar
pixhawk committed
130 131 132 133 134 135 136 137
}

/**
 * @brief Runs the thread
 *
 **/
void UDPLink::run()
{
138
    if(_hardwareConnect()) {
139
        exec();
140
    }
141
    if (_socket) {
142
        _deregisterZeroconf();
143 144
        _socket->close();
    }
pixhawk's avatar
pixhawk committed
145 146
}

147
void UDPLink::_restartConnection()
pixhawk's avatar
pixhawk committed
148
{
149 150 151 152 153
    if(this->isConnected())
    {
        _disconnect();
        _connect();
    }
pixhawk's avatar
pixhawk committed
154 155
}

156
QString UDPLink::getName() const
pixhawk's avatar
pixhawk committed
157
{
158
    return _udpConfig->name();
159 160
}

161
void UDPLink::_writeBytes(const QByteArray data)
162
{
163 164
    if (!_socket)
        return;
Gus Grubba's avatar
Gus Grubba committed
165 166 167
    // Send to all manually targeted systems
    foreach(UDPCLient* target, _udpConfig->targetHosts()) {
        // Skip it if it's part of the session clients below
168
        if(!contains_target(_sessionTargets, target->address, target->port)) {
Gus Grubba's avatar
Gus Grubba committed
169
            _writeDataGram(data, target);
170 171
        }
    }
Gus Grubba's avatar
Gus Grubba committed
172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188
    // Send to all connected systems
    foreach(UDPCLient* target, _sessionTargets) {
        _writeDataGram(data, target);
    }
}

void UDPLink::_writeDataGram(const QByteArray data, const UDPCLient* target)
{
    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());
    }
189 190
}

pixhawk's avatar
pixhawk committed
191 192 193
/**
 * @brief Read a number of bytes from the interface.
 **/
194
void UDPLink::readBytes()
pixhawk's avatar
pixhawk committed
195
{
196
    if (_socket) {
DonLakeFlyer's avatar
DonLakeFlyer committed
197 198 199 200 201 202 203 204 205 206 207 208 209 210
        return;
    }

    QByteArray databuffer;
    while (_socket->hasPendingDatagrams())
    {
        QByteArray datagram;
        datagram.resize(_socket->pendingDatagramSize());
        QHostAddress sender;
        quint16 senderPort;
        _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
211
            emit bytesReceived(this, databuffer);
DonLakeFlyer's avatar
DonLakeFlyer committed
212
            databuffer.clear();
dogmaphobic's avatar
dogmaphobic committed
213
        }
214
        _logInputDataRate(datagram.length(), QDateTime::currentMSecsSinceEpoch());
Gus Grubba's avatar
Gus Grubba committed
215
        // TODO: This doesn't validade the sender. Anything sending UDP packets to this port gets
216 217 218
        // 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
219 220 221 222
        QHostAddress asender = sender;
        if(is_ip_local(sender)) {
            asender = QHostAddress(QString("127.0.0.1"));
        }
223
        if(!contains_target(_sessionTargets, asender, senderPort)) {
Gus Grubba's avatar
Gus Grubba committed
224 225 226 227
            qDebug() << "Adding target" << asender << senderPort;
            UDPCLient* target = new UDPCLient(asender, senderPort);
            _sessionTargets.append(target);
        }
pixhawk's avatar
pixhawk committed
228
    }
dogmaphobic's avatar
dogmaphobic committed
229 230 231 232
    //-- Send whatever is left
    if(databuffer.size()) {
        emit bytesReceived(this, databuffer);
    }
pixhawk's avatar
pixhawk committed
233 234 235 236 237 238 239
}

/**
 * @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
240
void UDPLink::_disconnect(void)
pixhawk's avatar
pixhawk committed
241
{
242
    _running = false;
243
    quit();
244
    wait();
245 246 247 248
    if (_socket) {
        // Make sure delete happen on correct thread
        _socket->deleteLater();
        _socket = NULL;
249
        emit disconnected();
250 251
    }
    _connectState = false;
pixhawk's avatar
pixhawk committed
252 253 254 255 256 257 258
}

/**
 * @brief Connect the connection.
 *
 * @return True if connection has been established, false if connection couldn't be established.
 **/
259
bool UDPLink::_connect(void)
pixhawk's avatar
pixhawk committed
260
{
261
    if(this->isRunning() || _running)
262
    {
263
        _running = false;
264
        quit();
265
        wait();
266
    }
267
    _running = true;
268
    start(NormalPriority);
269
    return true;
oberion's avatar
oberion committed
270 271
}

272
bool UDPLink::_hardwareConnect()
oberion's avatar
oberion committed
273
{
274 275 276 277
    if (_socket) {
        delete _socket;
        _socket = NULL;
    }
278
    QHostAddress host = QHostAddress::AnyIPv4;
279
    _socket = new QUdpSocket(this);
280
    _socket->setProxy(QNetworkProxy::NoProxy);
281
    _connectState = _socket->bind(host, _udpConfig->localPort(), QAbstractSocket::ReuseAddressHint | QUdpSocket::ShareAddress);
282
    if (_connectState) {
283
        _socket->joinMulticastGroup(QHostAddress("224.0.0.1"));
dogmaphobic's avatar
dogmaphobic committed
284
        //-- Make sure we have a large enough IO buffers
Don Gagne's avatar
Don Gagne committed
285
#ifdef __mobile__
dogmaphobic's avatar
dogmaphobic committed
286 287 288 289 290 291
        _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
292
        _registerZeroconf(_udpConfig->localPort(), kZeroconfRegistration);
293
        QObject::connect(_socket, &QUdpSocket::readyRead, this, &UDPLink::readBytes);
294
        emit connected();
295
    } else {
Don Gagne's avatar
Don Gagne committed
296
        emit communicationError(tr("UDP Link Error"), tr("Error binding UDP port: %1").arg(_socket->errorString()));
297
    }
298
    return _connectState;
pixhawk's avatar
pixhawk committed
299 300 301 302 303 304 305
}

/**
 * @brief Check if connection is active.
 *
 * @return True if link is connected, false otherwise.
 **/
306
bool UDPLink::isConnected() const
307
{
308
    return _connectState;
pixhawk's avatar
pixhawk committed
309 310
}

311
qint64 UDPLink::getConnectionSpeed() const
pixhawk's avatar
pixhawk committed
312
{
313 314 315 316 317 318
    return 54000000; // 54 Mbit
}

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

321
qint64 UDPLink::getCurrentOutDataRate() const
pixhawk's avatar
pixhawk committed
322
{
323
    return 0;
pixhawk's avatar
pixhawk committed
324 325
}

326 327 328 329
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
330 331 332 333 334 335 336 337
                                                    regType.c_str(),
                                                    NULL,
                                                    NULL,
                                                    htons(port),
                                                    0,
                                                    NULL,
                                                    NULL,
                                                    NULL);
338 339
    if (result != kDNSServiceErr_NoError)
    {
340
        emit communicationError(tr("UDP Link Error"), tr("Error registering Zeroconf"));
341 342 343 344 345 346 347 348 349 350 351 352
        _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
353 354 355 356
    {
        DNSServiceRefDeallocate(_dnssServiceRef);
        _dnssServiceRef = NULL;
    }
357 358 359
#endif
}

360 361
//--------------------------------------------------------------------------
//-- UDPConfiguration
362

363
UDPConfiguration::UDPConfiguration(const QString& name) : LinkConfiguration(name)
364
{
365 366 367 368
    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
369
        addHost(targetHostIP, settings->udpTargetHostPort()->rawValue().toUInt());
370
    }
371 372
}

373
UDPConfiguration::UDPConfiguration(UDPConfiguration* source) : LinkConfiguration(source)
374
{
Gus Grubba's avatar
Gus Grubba committed
375 376 377 378 379 380
    _copyFrom(source);
}

UDPConfiguration::~UDPConfiguration()
{
    _clearTargetHosts();
381 382
}

383
void UDPConfiguration::copyFrom(LinkConfiguration *source)
384
{
385
    LinkConfiguration::copyFrom(source);
Gus Grubba's avatar
Gus Grubba committed
386 387 388 389 390
    _copyFrom(source);
}

void UDPConfiguration::_copyFrom(LinkConfiguration *source)
{
391
    UDPConfiguration* usource = dynamic_cast<UDPConfiguration*>(source);
DonLakeFlyer's avatar
DonLakeFlyer committed
392 393
    if (usource) {
        _localPort = usource->localPort();
Gus Grubba's avatar
Gus Grubba committed
394 395
        _clearTargetHosts();
        foreach(UDPCLient* target, usource->targetHosts()) {
396
            if(!contains_target(_targetHosts, target->address, target->port)) {
Gus Grubba's avatar
Gus Grubba committed
397 398 399
                UDPCLient* newTarget = new UDPCLient(target);
                _targetHosts.append(newTarget);
            }
DonLakeFlyer's avatar
DonLakeFlyer committed
400 401 402
        }
    } else {
        qWarning() << "Internal error";
403 404 405
    }
}

Gus Grubba's avatar
Gus Grubba committed
406 407
void UDPConfiguration::_clearTargetHosts()
{
408 409
    qDeleteAll(_targetHosts);
    _targetHosts.clear();
Gus Grubba's avatar
Gus Grubba committed
410 411
}

412 413 414
/**
 * @param host Hostname in standard formatt, e.g. localhost:14551 or 192.168.1.1:14551
 */
415
void UDPConfiguration::addHost(const QString host)
416
{
417
    // Handle x.x.x.x:p
418 419
    if (host.contains(":"))
    {
Gus Grubba's avatar
Gus Grubba committed
420
        addHost(host.split(":").first(), host.split(":").last().toUInt());
421
    }
422
    // If no port, use default
423 424
    else
    {
Gus Grubba's avatar
Gus Grubba committed
425
        addHost(host, _localPort);
426 427 428
    }
}

Gus Grubba's avatar
Gus Grubba committed
429
void UDPConfiguration::addHost(const QString& host, quint16 port)
430
{
Gus Grubba's avatar
Gus Grubba committed
431 432 433
    QString ipAdd = get_ip_address(host);
    if(ipAdd.isEmpty()) {
        qWarning() << "UDP:" << "Could not resolve host:" << host << "port:" << port;
dogmaphobic's avatar
dogmaphobic committed
434
    } else {
Gus Grubba's avatar
Gus Grubba committed
435
        QHostAddress address(ipAdd);
436
        if(!contains_target(_targetHosts, address, port)) {
Gus Grubba's avatar
Gus Grubba committed
437 438 439
            UDPCLient* newTarget = new UDPCLient(address, port);
            _targetHosts.append(newTarget);
            _updateHostList();
dogmaphobic's avatar
dogmaphobic committed
440 441
        }
    }
442 443
}

444
void UDPConfiguration::removeHost(const QString host)
445
{
Gus Grubba's avatar
Gus Grubba committed
446 447 448 449 450 451 452 453 454 455 456 457 458
    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;
            }
        }
459
    }
Gus Grubba's avatar
Gus Grubba committed
460
    qWarning() << "UDP:" << "Could not remove unknown host:" << host;
461
    _updateHostList();
462 463 464 465 466 467 468 469 470 471 472
}

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
473 474 475 476 477 478 479
    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);
480 481 482 483 484 485
    }
    settings.endGroup();
}

void UDPConfiguration::loadSettings(QSettings& settings, const QString& root)
{
486
    AutoConnectSettings* acSettings = qgcApp()->toolbox()->settingsManager()->autoConnectSettings();
Gus Grubba's avatar
Gus Grubba committed
487
    _clearTargetHosts();
dogmaphobic's avatar
dogmaphobic committed
488
    settings.beginGroup(root);
489
    _localPort = (quint16)settings.value("port", acSettings->udpListenPort()->rawValue().toInt()).toUInt();
490 491 492 493 494
    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
495
            addHost(settings.value(hkey).toString(), settings.value(pkey).toUInt());
496 497 498
        }
    }
    settings.endGroup();
499
    _updateHostList();
500 501 502 503 504 505 506 507 508 509
}

void UDPConfiguration::updateSettings()
{
    if(_link) {
        UDPLink* ulink = dynamic_cast<UDPLink*>(_link);
        if(ulink) {
            ulink->_restartConnection();
        }
    }
510
}
511 512 513 514

void UDPConfiguration::_updateHostList()
{
    _hostList.clear();
Gus Grubba's avatar
Gus Grubba committed
515 516 517 518
    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;
519 520 521
    }
    emit hostListChanged();
}