UDPLink.cc 14.3 KB
Newer Older
1 2
/****************************************************************************
 *
Gus Grubba's avatar
Gus Grubba committed
3
 * (c) 2009-2020 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
4 5 6 7 8
 *
 * 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
#include <QtGlobal>
pixhawk's avatar
pixhawk committed
11 12 13 14
#include <QTimer>
#include <QList>
#include <QDebug>
#include <QMutexLocker>
15
#include <QNetworkProxy>
16
#include <QNetworkInterface>
pixhawk's avatar
pixhawk committed
17
#include <iostream>
18
#include <QHostInfo>
19

pixhawk's avatar
pixhawk committed
20
#include "UDPLink.h"
21
#include "QGC.h"
22 23 24
#include "QGCApplication.h"
#include "SettingsManager.h"
#include "AutoConnectSettings.h"
pixhawk's avatar
pixhawk committed
25

26 27
static const char* kZeroconfRegistration = "_qgroundcontrol._udp";

28 29 30
static bool is_ip(const QString& address)
{
    int a,b,c,d;
31
    if (sscanf(address.toStdString().c_str(), "%d.%d.%d.%d", &a, &b, &c, &d) != 4 && strcmp("::1", address.toStdString().c_str())) {
32
        return false;
33 34 35
    } else {
        return true;
    }
36 37 38 39
}

static QString get_ip_address(const QString& address)
{
40
    if (is_ip(address)) {
41
        return address;
42
    }
43 44
    // Need to look it up
    QHostInfo info = QHostInfo::fromName(address);
45
    if (info.error() == QHostInfo::NoError) {
46
        QList<QHostAddress> hostAddresses = info.addresses();
47
        for (int i=0; i<hostAddresses.size(); i++) {
48
            // Exclude all IPv6 addresses
49
            if (!hostAddresses.at(i).toString().contains(":")) {
50 51 52 53
                return hostAddresses.at(i).toString();
            }
        }
    }
54
    return QString();
55 56
}

57
static bool contains_target(const QList<UDPCLient*> list, const QHostAddress& address, quint16 port)
Gus Grubba's avatar
Gus Grubba committed
58
{
59 60 61
    for (int i=0; i<list.count(); i++) {
        UDPCLient* target = list[i];
        if (target->address == address && target->port == port) {
Gus Grubba's avatar
Gus Grubba committed
62 63 64 65 66 67
            return true;
        }
    }
    return false;
}

68
UDPLink::UDPLink(SharedLinkConfigurationPtr& config)
69 70 71
    : LinkInterface     (config)
    , _running          (false)
    , _socket           (nullptr)
72
    , _udpConfig        (qobject_cast<UDPConfiguration*>(config.get()))
73
    , _connectState     (false)
74 75 76
#if defined(QGC_ZEROCONF_ENABLED)
    , _dnssServiceRef   (nullptr)
#endif
pixhawk's avatar
pixhawk committed
77
{
DonLakeFlyer's avatar
DonLakeFlyer committed
78 79 80
    if (!_udpConfig) {
        qWarning() << "Internal error";
    }
81 82 83 84
    auto allAddresses = QNetworkInterface::allAddresses();
    for (int i=0; i<allAddresses.count(); i++) {
        QHostAddress &address = allAddresses[i];
        _localAddresses.append(QHostAddress(address));
85
    }
86
    moveToThread(this);
pixhawk's avatar
pixhawk committed
87 88 89 90
}

UDPLink::~UDPLink()
{
91 92
    qDebug() << "~UDPLink";
    disconnect();
Lorenz Meier's avatar
Lorenz Meier committed
93
    // Tell the thread to exit
94
    _running = false;
Gus Grubba's avatar
Gus Grubba committed
95
    // Clear client list
96 97
    qDeleteAll(_sessionTargets);
    _sessionTargets.clear();
98
    quit();
Lorenz Meier's avatar
Lorenz Meier committed
99 100
    // Wait for it to exit
    wait();
101
    this->deleteLater();
pixhawk's avatar
pixhawk committed
102 103 104 105
}

void UDPLink::run()
{
106
    if (_hardwareConnect()) {
107
        exec();
108
    }
109
    if (_socket) {
110
        _deregisterZeroconf();
111 112
        _socket->close();
    }
pixhawk's avatar
pixhawk committed
113 114
}

115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130
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.
131 132
    for (int i=0; i<_localAddresses.count(); i++) {
        QHostAddress &address = _localAddresses[i];
133 134 135 136 137 138 139 140
        if (address == add) {
            // This is a local address of the same host
            return true;
        }
    }
    return false;
}

141
void UDPLink::_writeBytes(const QByteArray data)
142
{
143
    if (!_socket) {
144
        return;
145
    }
146
    emit bytesSent(this, data);
147 148 149

    QMutexLocker locker(&_sessionTargetsMutex);

Gus Grubba's avatar
Gus Grubba committed
150
    // Send to all manually targeted systems
151 152
    for (int i=0; i<_udpConfig->targetHosts().count(); i++) {
        UDPCLient* target = _udpConfig->targetHosts()[i];
Gus Grubba's avatar
Gus Grubba committed
153
        // Skip it if it's part of the session clients below
154
        if(!contains_target(_sessionTargets, target->address, target->port)) {
Gus Grubba's avatar
Gus Grubba committed
155
            _writeDataGram(data, target);
156 157
        }
    }
Gus Grubba's avatar
Gus Grubba committed
158
    // Send to all connected systems
159
    for(UDPCLient* target: _sessionTargets) {
Gus Grubba's avatar
Gus Grubba committed
160 161 162 163 164 165
        _writeDataGram(data, target);
    }
}

void UDPLink::_writeDataGram(const QByteArray data, const UDPCLient* target)
{
166
    //qDebug() << "UDP Out" << target->address << target->port;
Gus Grubba's avatar
Gus Grubba committed
167 168 169
    if(_socket->writeDatagram(data, target->address, target->port) < 0) {
        qWarning() << "Error writing to" << target->address << target->port;
    }
170 171
}

172
void UDPLink::readBytes()
pixhawk's avatar
pixhawk committed
173
{
174
    if (!_socket) {
DonLakeFlyer's avatar
DonLakeFlyer committed
175 176 177 178 179 180 181 182 183
        return;
    }
    QByteArray databuffer;
    while (_socket->hasPendingDatagrams())
    {
        QByteArray datagram;
        datagram.resize(_socket->pendingDatagramSize());
        QHostAddress sender;
        quint16 senderPort;
184
        //-- 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
185 186 187
        _socket->readDatagram(datagram.data(), datagram.size(), &sender, &senderPort);
        databuffer.append(datagram);
        //-- Wait a bit before sending it over
188
        if (databuffer.size() > 10 * 1024) {
dogmaphobic's avatar
dogmaphobic committed
189
            emit bytesReceived(this, databuffer);
DonLakeFlyer's avatar
DonLakeFlyer committed
190
            databuffer.clear();
dogmaphobic's avatar
dogmaphobic committed
191
        }
Gus Grubba's avatar
Gus Grubba committed
192
        // TODO: This doesn't validade the sender. Anything sending UDP packets to this port gets
193 194 195
        // 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
196
        QHostAddress asender = sender;
197
        if(_isIpLocal(sender)) {
Gus Grubba's avatar
Gus Grubba committed
198 199
            asender = QHostAddress(QString("127.0.0.1"));
        }
200 201
        QMutexLocker locker(&_sessionTargetsMutex);
        if (!contains_target(_sessionTargets, asender, senderPort)) {
Gus Grubba's avatar
Gus Grubba committed
202 203 204 205
            qDebug() << "Adding target" << asender << senderPort;
            UDPCLient* target = new UDPCLient(asender, senderPort);
            _sessionTargets.append(target);
        }
206
        locker.unlock();
pixhawk's avatar
pixhawk committed
207
    }
dogmaphobic's avatar
dogmaphobic committed
208
    //-- Send whatever is left
209
    if (databuffer.size()) {
dogmaphobic's avatar
dogmaphobic committed
210 211
        emit bytesReceived(this, databuffer);
    }
pixhawk's avatar
pixhawk committed
212 213
}

214
void UDPLink::disconnect(void)
pixhawk's avatar
pixhawk committed
215
{
216
    _running = false;
217
    quit();
218
    wait();
219
    if (_socket) {
220 221
        // This prevents stale signal from calling the link after it has been deleted
        QObject::disconnect(_socket, &QUdpSocket::readyRead, this, &UDPLink::readBytes);
222 223
        // Make sure delete happen on correct thread
        _socket->deleteLater();
224
        _socket = nullptr;
225
        emit disconnected();
226 227
    }
    _connectState = false;
pixhawk's avatar
pixhawk committed
228 229
}

230
bool UDPLink::_connect(void)
pixhawk's avatar
pixhawk committed
231
{
232
    if (this->isRunning() || _running) {
233
        _running = false;
234
        quit();
235
        wait();
236
    }
237
    _running = true;
238
    start(NormalPriority);
239
    return true;
oberion's avatar
oberion committed
240 241
}

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

271
bool UDPLink::isConnected() const
272
{
273
    return _connectState;
pixhawk's avatar
pixhawk committed
274 275
}

276 277 278 279
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
280 281 282 283 284 285 286 287
                                                    regType.c_str(),
                                                    NULL,
                                                    NULL,
                                                    htons(port),
                                                    0,
                                                    NULL,
                                                    NULL,
                                                    NULL);
288 289
    if (result != kDNSServiceErr_NoError)
    {
290
        emit communicationError(tr("UDP Link Error"), tr("Error registering Zeroconf"));
291 292 293 294 295 296 297 298 299 300 301 302
        _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
303 304 305 306
    {
        DNSServiceRefDeallocate(_dnssServiceRef);
        _dnssServiceRef = NULL;
    }
307 308 309
#endif
}

310 311
//--------------------------------------------------------------------------
//-- UDPConfiguration
312

313
UDPConfiguration::UDPConfiguration(const QString& name) : LinkConfiguration(name)
314
{
315 316 317 318
    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
319
        addHost(targetHostIP, settings->udpTargetHostPort()->rawValue().toUInt());
320
    }
321 322
}

323
UDPConfiguration::UDPConfiguration(UDPConfiguration* source) : LinkConfiguration(source)
324
{
Gus Grubba's avatar
Gus Grubba committed
325 326 327 328 329 330
    _copyFrom(source);
}

UDPConfiguration::~UDPConfiguration()
{
    _clearTargetHosts();
331 332
}

333
void UDPConfiguration::copyFrom(LinkConfiguration *source)
334
{
335
    LinkConfiguration::copyFrom(source);
Gus Grubba's avatar
Gus Grubba committed
336 337 338 339 340
    _copyFrom(source);
}

void UDPConfiguration::_copyFrom(LinkConfiguration *source)
{
341
    auto* usource = qobject_cast<UDPConfiguration*>(source);
DonLakeFlyer's avatar
DonLakeFlyer committed
342 343
    if (usource) {
        _localPort = usource->localPort();
Gus Grubba's avatar
Gus Grubba committed
344
        _clearTargetHosts();
345 346
        for (int i=0; i<usource->targetHosts().count(); i++) {
            UDPCLient* target = usource->targetHosts()[i];
347
            if(!contains_target(_targetHosts, target->address, target->port)) {
Gus Grubba's avatar
Gus Grubba committed
348 349
                UDPCLient* newTarget = new UDPCLient(target);
                _targetHosts.append(newTarget);
350
                _updateHostList();
Gus Grubba's avatar
Gus Grubba committed
351
            }
DonLakeFlyer's avatar
DonLakeFlyer committed
352 353 354
        }
    } else {
        qWarning() << "Internal error";
355 356 357
    }
}

Gus Grubba's avatar
Gus Grubba committed
358 359
void UDPConfiguration::_clearTargetHosts()
{
360 361
    qDeleteAll(_targetHosts);
    _targetHosts.clear();
Gus Grubba's avatar
Gus Grubba committed
362 363
}

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

Gus Grubba's avatar
Gus Grubba committed
378
void UDPConfiguration::addHost(const QString& host, quint16 port)
379
{
Gus Grubba's avatar
Gus Grubba committed
380
    QString ipAdd = get_ip_address(host);
381
    if (ipAdd.isEmpty()) {
Gus Grubba's avatar
Gus Grubba committed
382
        qWarning() << "UDP:" << "Could not resolve host:" << host << "port:" << port;
dogmaphobic's avatar
dogmaphobic committed
383
    } else {
Gus Grubba's avatar
Gus Grubba committed
384
        QHostAddress address(ipAdd);
385
        if(!contains_target(_targetHosts, address, port)) {
Gus Grubba's avatar
Gus Grubba committed
386 387 388
            UDPCLient* newTarget = new UDPCLient(address, port);
            _targetHosts.append(newTarget);
            _updateHostList();
dogmaphobic's avatar
dogmaphobic committed
389 390
        }
    }
391 392
}

393
void UDPConfiguration::removeHost(const QString host)
394
{
395
    if (host.contains(":")) {
Gus Grubba's avatar
Gus Grubba committed
396 397
        QHostAddress address = QHostAddress(get_ip_address(host.split(":").first()));
        quint16 port = host.split(":").last().toUInt();
398
        for (int i=0; i<_targetHosts.size(); i++) {
Gus Grubba's avatar
Gus Grubba committed
399 400 401 402 403 404 405 406
            UDPCLient* target = _targetHosts.at(i);
            if(target->address == address && target->port == port) {
                _targetHosts.removeAt(i);
                delete target;
                _updateHostList();
                return;
            }
        }
407
    }
Gus Grubba's avatar
Gus Grubba committed
408
    qWarning() << "UDP:" << "Could not remove unknown host:" << host;
409
    _updateHostList();
410 411 412 413 414 415 416 417 418 419 420
}

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
421
    settings.setValue("hostCount", _targetHosts.size());
422
    for (int i=0; i<_targetHosts.size(); i++) {
Gus Grubba's avatar
Gus Grubba committed
423 424 425 426 427
        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);
428 429 430 431 432 433
    }
    settings.endGroup();
}

void UDPConfiguration::loadSettings(QSettings& settings, const QString& root)
{
434
    AutoConnectSettings* acSettings = qgcApp()->toolbox()->settingsManager()->autoConnectSettings();
Gus Grubba's avatar
Gus Grubba committed
435
    _clearTargetHosts();
dogmaphobic's avatar
dogmaphobic committed
436
    settings.beginGroup(root);
437
    _localPort = (quint16)settings.value("port", acSettings->udpListenPort()->rawValue().toInt()).toUInt();
438
    int hostCount = settings.value("hostCount", 0).toInt();
439
    for (int i=0; i<hostCount; i++) {
440 441 442
        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
443
            addHost(settings.value(hkey).toString(), settings.value(pkey).toUInt());
444 445 446
        }
    }
    settings.endGroup();
447
    _updateHostList();
448 449
}

450 451 452
void UDPConfiguration::_updateHostList()
{
    _hostList.clear();
453
    for (int i=0; i<_targetHosts.size(); i++) {
Gus Grubba's avatar
Gus Grubba committed
454 455 456
        UDPCLient* target = _targetHosts.at(i);
        QString host = QString("%1").arg(target->address.toString()) + ":" + QString("%1").arg(target->port);
        _hostList << host;
457 458 459
    }
    emit hostListChanged();
}