QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
UDPLink.cc
Go to the documentation of this file.
1#include "UDPLink.h"
4#include "QGCNetworkHelper.h"
5#include "SettingsManager.h"
6
7#include <QtCore/QMutexLocker>
8#include <QtCore/QThread>
9#include <QtNetwork/QHostInfo>
10#include <QtNetwork/QNetworkDatagram>
11#include <QtNetwork/QNetworkInterface>
12#include <QtNetwork/QNetworkProxy>
13#include <QtNetwork/QUdpSocket>
14
15QGC_LOGGING_CATEGORY(UDPLinkLog, "Comms.UDPLink")
16
17namespace {
18 constexpr int BUFFER_TRIGGER_SIZE = 10 * 1024;
19 constexpr int RECEIVE_TIME_LIMIT_MS = 50;
20
21 bool containsTarget(const QList<std::shared_ptr<UDPClient>> &list, const QHostAddress &address, quint16 port)
22 {
23 for (const std::shared_ptr<UDPClient> &target : list) {
24 if ((target->address == address) && (target->port == port)) {
25 return true;
26 }
27 }
28
29 return false;
30 }
31
32 bool containsHost(const QList<std::shared_ptr<UDPClient>> &list, const QString &hostname, quint16 port)
33 {
34 for (const std::shared_ptr<UDPClient> &target : list) {
35 if ((target->hostname == hostname) && (target->port == port)) {
36 return true;
37 }
38 }
39
40 return false;
41 }
42}
43
44/*===========================================================================*/
45
46UDPConfiguration::UDPConfiguration(const QString &name, QObject *parent)
47 : LinkConfiguration(name, parent)
48{
49}
50
52 : LinkConfiguration(source, parent)
53{
54 qCDebug(UDPLinkLog) << this;
55
57}
58
60{
61 _targetHosts.clear();
62
63 qCDebug(UDPLinkLog) << this;
64}
65
67{
68 const UDPConfiguration *udpSource = qobject_cast<const UDPConfiguration*>(source);
69 if (!udpSource) {
70 qCWarning(UDPLinkLog) << "Invalid source configuration type";
71 return;
72 }
73
75
76 setLocalPort(udpSource->localPort());
78 _targetHosts.clear();
79
80 for (const std::shared_ptr<UDPClient> &target : udpSource->targetHosts()) {
81 if (!containsHost(_targetHosts, target->hostname, target->port)) {
82 _targetHosts.append(std::make_shared<UDPClient>(target.get()));
83 }
84 }
85
86 _updateHostList();
87}
88
89void UDPConfiguration::loadSettings(QSettings &settings, const QString &root)
90{
91 settings.beginGroup(root);
92
93 setLocalPort(static_cast<quint16>(settings.value("port", SettingsManager::instance()->autoConnectSettings()->udpListenPort()->rawValue().toUInt()).toUInt()));
94
95 _targetHosts.clear();
96 const qsizetype hostCount = settings.value("hostCount", 0).toUInt();
97 for (qsizetype i = 0; i < hostCount; i++) {
98 const QString hkey = QStringLiteral("host%1").arg(i);
99 const QString pkey = QStringLiteral("port%1").arg(i);
100 if (settings.contains(hkey) && settings.contains(pkey)) {
101 addHost(settings.value(hkey).toString(), settings.value(pkey).toUInt());
102 }
103 }
104
105 _updateHostList();
106
107 settings.endGroup();
108}
109
110void UDPConfiguration::saveSettings(QSettings &settings, const QString &root) const
111{
112 settings.beginGroup(root);
113
114 settings.setValue(QStringLiteral("hostCount"), _targetHosts.size());
115 settings.setValue(QStringLiteral("port"), _localPort);
116
117 for (qsizetype i = 0; i < _targetHosts.size(); i++) {
118 const std::shared_ptr<UDPClient> target = _targetHosts.at(i);
119 const QString hkey = QStringLiteral("host%1").arg(i);
120 const QString hostValue = target->hostname.isEmpty() ? target->address.toString() : target->hostname;
121 settings.setValue(hkey, hostValue);
122 const QString pkey = QStringLiteral("port%1").arg(i);
123 settings.setValue(pkey, target->port);
124 }
125
126 settings.endGroup();
127}
128
129void UDPConfiguration::addHost(const QString &host)
130{
131 if (host.contains(":")) {
132 const QStringList hostInfo = host.split(":");
133 if (hostInfo.size() != 2) {
134 qCWarning(UDPLinkLog) << "Invalid host format:" << host;
135 return;
136 }
137
138 const QString address = hostInfo.constFirst();
139 const quint16 port = hostInfo.constLast().toUInt();
140
141 addHost(address, port);
142 } else {
143 addHost(host, _localPort);
144 }
145}
146
147void UDPConfiguration::addHost(const QString &host, quint16 port)
148{
149 const QString cleanHost = host.trimmed();
150 if (cleanHost.isEmpty()) {
151 return;
152 }
153
154 if (containsHost(_targetHosts, cleanHost, port)) {
155 return;
156 }
157
158 // Hostnames stay unresolved (null address) until resolveHosts() on connect; DNS lookup blocks.
159 const QHostAddress address(cleanHost);
160
161 _targetHosts.append(std::make_shared<UDPClient>(cleanHost, address, port));
162 _updateHostList();
163}
164
165void UDPConfiguration::removeHost(const QString &host)
166{
167 if (host.contains(":")) {
168 const QStringList hostInfo = host.split(":");
169 if (hostInfo.size() != 2) {
170 qCWarning(UDPLinkLog) << "Invalid host format:" << host;
171 return;
172 }
173
174 removeHost(hostInfo.constFirst(), hostInfo.constLast().toUInt());
175 } else {
176 removeHost(host, _localPort);
177 }
178}
179
180void UDPConfiguration::removeHost(const QString &host, quint16 port)
181{
182 const QString cleanHost = host.trimmed();
183
184 for (qsizetype i = 0; i < _targetHosts.size(); ++i) {
185 const std::shared_ptr<UDPClient> &target = _targetHosts[i];
186 if ((target->hostname == cleanHost) && (target->port == port)) {
187 _targetHosts.removeAt(i);
188 _updateHostList();
189 return;
190 }
191 }
192
193 const QHostAddress resolved(_getIpAddress(cleanHost));
194 if (!resolved.isNull()) {
195 for (qsizetype i = 0; i < _targetHosts.size(); ++i) {
196 const std::shared_ptr<UDPClient> &target = _targetHosts[i];
197 if ((target->address == resolved) && (target->port == port)) {
198 _targetHosts.removeAt(i);
199 _updateHostList();
200 return;
201 }
202 }
203 }
204
205 qCWarning(UDPLinkLog) << "Could not remove unknown host:" << host << "port:" << port;
206}
207
208void UDPConfiguration::_updateHostList()
209{
210 _hostList.clear();
211 for (const std::shared_ptr<UDPClient> &target : _targetHosts) {
212 const QString name = target->hostname.isEmpty() ? target->address.toString() : target->hostname;
213 const QString host = name + ":" + QString::number(target->port);
214 _hostList.append(host);
215 }
216
217 emit hostListChanged();
218}
219
221{
222 QStringList unresolved;
223 for (const std::shared_ptr<UDPClient> &target : _targetHosts) {
224 if (target->hostname.isEmpty()) {
225 continue;
226 }
227
228 const QString ipAdd = _getIpAddress(target->hostname);
229 if (!ipAdd.isEmpty()) {
230 target->address = QHostAddress(ipAdd);
231 } else {
232 target->address = QHostAddress();
233 unresolved.append(target->hostname);
234 }
235 }
236
237 return unresolved;
238}
239
240QString UDPConfiguration::_getIpAddress(const QString &address)
241{
242 const QHostAddress host(address);
243 if (!host.isNull()) {
244 return address;
245 }
246
247 const QHostInfo info = QHostInfo::fromName(address);
248 if (info.error() != QHostInfo::NoError) {
249 return QString();
250 }
251
252 const QList<QHostAddress> hostAddresses = info.addresses();
253 for (const QHostAddress &hostAddress : hostAddresses) {
254 if (hostAddress.protocol() == QAbstractSocket::NetworkLayerProtocol::IPv4Protocol) {
255 return hostAddress.toString();
256 }
257 }
258
259 return QString();
260}
261
262/*===========================================================================*/
263
264const QHostAddress UDPWorker::_multicastGroup = QHostAddress(QStringLiteral("224.0.0.1"));
265
267 : QObject(parent)
268 , _udpConfig(config)
269{
270 qCDebug(UDPLinkLog) << this;
271}
272
274{
276
277 qCDebug(UDPLinkLog) << this;
278}
279
281{
282 // Called cross-thread from UDPLink; must not touch the thread-affine _socket
283 return _isConnected;
284}
285
287{
288 if (!_socket) {
289 _socket = new QUdpSocket(this);
290 }
291
292 const QList<QHostAddress> localAddresses = QNetworkInterface::allAddresses();
293 _localAddresses = QSet(localAddresses.constBegin(), localAddresses.constEnd());
294
295 _socket->setProxy(QNetworkProxy::NoProxy);
296
297 (void) connect(_socket, &QUdpSocket::connected, this, &UDPWorker::_onSocketConnected);
298 (void) connect(_socket, &QUdpSocket::disconnected, this, &UDPWorker::_onSocketDisconnected);
299 (void) connect(_socket, &QUdpSocket::readyRead, this, &UDPWorker::_onSocketReadyRead);
300 (void) connect(_socket, &QUdpSocket::errorOccurred, this, &UDPWorker::_onSocketErrorOccurred);
301 (void) connect(_socket, &QUdpSocket::stateChanged, this, [this](QUdpSocket::SocketState state) {
302 qCDebug(UDPLinkLog) << "UDP State Changed:" << state;
303 switch (state) {
304 case QAbstractSocket::BoundState:
305 _onSocketConnected();
306 break;
307 case QAbstractSocket::ClosingState:
308 case QAbstractSocket::UnconnectedState:
309 _onSocketDisconnected();
310 break;
311 default:
312 break;
313 }
314 });
315
316 if (UDPLinkLog().isDebugEnabled()) {
317 // (void) connect(_socket, &QUdpSocket::bytesWritten, this, &UDPWorker::_onSocketBytesWritten);
318
319 (void) QObject::connect(_socket, &QUdpSocket::hostFound, this, []() {
320 qCDebug(UDPLinkLog) << "UDP Host Found";
321 });
322 }
323}
324
326{
327 if (isConnected()) {
328 qCWarning(UDPLinkLog) << "Already connected to" << _udpConfig->localPort();
329 return;
330 }
331
332 _errorEmitted = false;
333
334 const QStringList unresolvedHosts = _udpConfig->resolveHosts();
335 if (!unresolvedHosts.isEmpty()) {
336 const QString error = tr("Could not resolve host: %1").arg(unresolvedHosts.join(QStringLiteral(", ")));
337 if (_udpConfig->requireResolvedHosts()) {
338 emit errorOccurred(error);
339 _errorEmitted = true;
340 _onSocketDisconnected();
341 return;
342 }
343 qCWarning(UDPLinkLog) << error << "- listening without those targets";
344 }
345
346 qCDebug(UDPLinkLog) << "Attempting to bind to port:" << _udpConfig->localPort();
347 const bool bindSuccess = _socket->bind(QHostAddress::AnyIPv4, _udpConfig->localPort(), QAbstractSocket::ReuseAddressHint | QAbstractSocket::ShareAddress);
348 if (!bindSuccess) {
349 qCWarning(UDPLinkLog) << "Failed to bind UDP socket to port" << _udpConfig->localPort();
350
351 if (!_errorEmitted) {
352 emit errorOccurred(tr("Failed to bind UDP socket to port"));
353 _errorEmitted = true;
354 }
355
356 _onSocketDisconnected();
357 return;
358 }
359
360 qCDebug(UDPLinkLog) << "Attempting to join multicast group:" << _multicastGroup.toString();
361 const bool joinSuccess = _socket->joinMulticastGroup(_multicastGroup);
362 if (!joinSuccess) {
363 qCWarning(UDPLinkLog) << "Failed to join multicast group" << _multicastGroup.toString();
364 }
365
366}
367
369{
370 if (!isConnected()) {
371 qCDebug(UDPLinkLog) << "Already disconnected";
372 return;
373 }
374
375 qCDebug(UDPLinkLog) << "Disconnecting UDP link";
376
377 (void) _socket->leaveMulticastGroup(_multicastGroup);
378 _socket->close();
379
380 _sessionTargets.clear();
381}
382
383void UDPWorker::writeData(const QByteArray &data)
384{
385 if (!isConnected()) {
386 emit errorOccurred(tr("Could Not Send Data - Link is Disconnected!"));
387 return;
388 }
389
390 QMutexLocker locker(&_sessionTargetsMutex);
391
392 // Send to all manually targeted systems
393 for (const std::shared_ptr<UDPClient> &target : _udpConfig->targetHosts()) {
394 if (target->address.isNull()) {
395 continue;
396 }
397 if (!containsTarget(_sessionTargets, target->address, target->port)) {
398 if (_socket->writeDatagram(data, target->address, target->port) < 0) {
399 qCWarning(UDPLinkLog) << "Could Not Send Data - Write Failed!";
400 }
401 }
402 }
403
404 // Send to all connected systems
405 for (const std::shared_ptr<UDPClient> &target: _sessionTargets) {
406 if (_socket->writeDatagram(data, target->address, target->port) < 0) {
407 qCWarning(UDPLinkLog) << "Could Not Send Data - Write Failed!";
408 }
409 }
410
411 locker.unlock();
412
413 emit dataSent(data);
414}
415
416void UDPWorker::_onSocketConnected()
417{
418 qCDebug(UDPLinkLog) << "UDP connected to" << _udpConfig->localPort();
419 _isConnected = true;
420 _errorEmitted = false;
421 emit connected();
422}
423
424void UDPWorker::_onSocketDisconnected()
425{
426 qCDebug(UDPLinkLog) << "UDP disconnected from" << _udpConfig->localPort();
427 _isConnected = false;
428 _errorEmitted = false;
429 emit disconnected();
430}
431
432void UDPWorker::_onSocketReadyRead()
433{
434 if (!isConnected()) {
435 emit errorOccurred(tr("Could Not Read Data - Link is Disconnected!"));
436 return;
437 }
438
439 const qint64 byteCount = _socket->pendingDatagramSize();
440 if (byteCount <= 0) {
441 emit errorOccurred(tr("Could Not Read Data - No Data Available!"));
442 return;
443 }
444
445 QByteArray buffer;
446 buffer.reserve(BUFFER_TRIGGER_SIZE);
447 QElapsedTimer timer;
448 timer.start();
449 bool received = false;
450 while (_socket->hasPendingDatagrams()) {
451 const QNetworkDatagram datagramIn = _socket->receiveDatagram();
452 if (datagramIn.isNull() || datagramIn.data().isEmpty()) {
453 continue;
454 }
455
456 (void) buffer.append(datagramIn.data());
457
458 if ((buffer.size() > BUFFER_TRIGGER_SIZE) || (timer.elapsed() > RECEIVE_TIME_LIMIT_MS)) {
459 received = true;
460 emit dataReceived(buffer);
461 buffer.clear();
462 (void) timer.restart();
463 }
464
465 const bool ipLocal = datagramIn.senderAddress().isLoopback() || _localAddresses.contains(datagramIn.senderAddress());
466 const QHostAddress senderAddress = ipLocal ? QHostAddress(QHostAddress::SpecialAddress::LocalHost) : datagramIn.senderAddress();
467
468 QMutexLocker locker(&_sessionTargetsMutex);
469 if (!containsTarget(_sessionTargets, senderAddress, datagramIn.senderPort())) {
470 qCDebug(UDPLinkLog) << "UDP Adding target:" << senderAddress << datagramIn.senderPort();
471 _sessionTargets.append(std::make_shared<UDPClient>(senderAddress, datagramIn.senderPort()));
472 }
473 locker.unlock();
474 }
475
476 if (!received && buffer.isEmpty()) {
477 qCWarning(UDPLinkLog) << "No Data Available to Read!";
478 return;
479 }
480
481 emit dataReceived(buffer);
482}
483
484void UDPWorker::_onSocketBytesWritten(qint64 bytes)
485{
486 qCDebug(UDPLinkLog) << "Wrote" << bytes << "bytes";
487}
488
489void UDPWorker::_onSocketErrorOccurred(QUdpSocket::SocketError error)
490{
491 const QString errorString = _socket->errorString();
492 qCWarning(UDPLinkLog) << "UDP Link error:" << error << _socket->errorString();
493
494 if (!_errorEmitted) {
496 _errorEmitted = true;
497 }
498}
499
500/*===========================================================================*/
501
503 : LinkInterface(config, parent)
504 , _udpConfig(qobject_cast<const UDPConfiguration*>(config.get()))
505 , _worker(new UDPWorker(_udpConfig))
506 , _workerThread(new QThread(this))
507{
508 qCDebug(UDPLinkLog) << this;
509
510 _workerThread->setObjectName(QStringLiteral("UDP_%1").arg(_udpConfig->name()));
511
512 _worker->moveToThread(_workerThread);
513
514 (void) connect(_workerThread, &QThread::started, _worker, &UDPWorker::setupSocket);
515 (void) connect(_workerThread, &QThread::finished, _worker, &QObject::deleteLater);
516
517 (void) connect(_worker, &UDPWorker::connected, this, &UDPLink::_onConnected, Qt::QueuedConnection);
518 (void) connect(_worker, &UDPWorker::disconnected, this, &UDPLink::_onDisconnected, Qt::QueuedConnection);
519 (void) connect(_worker, &UDPWorker::errorOccurred, this, &UDPLink::_onErrorOccurred, Qt::QueuedConnection);
520 (void) connect(_worker, &UDPWorker::dataReceived, this, &UDPLink::_onDataReceived, Qt::QueuedConnection);
521 (void) connect(_worker, &UDPWorker::dataSent, this, &UDPLink::_onDataSent, Qt::QueuedConnection);
522
523 _workerThread->start();
524}
525
527{
528 if (isConnected()) {
529 // Queued, not blocking: keeps a wedged worker from hanging the destructor before
530 // _shutdownWorkerThread can bound the wait. If quit() beats the queued call,
531 // ~UDPWorker still disconnects on thread finish.
532 (void) QMetaObject::invokeMethod(_worker, "disconnectLink", Qt::QueuedConnection);
533 _onDisconnected();
534 }
535
536 // No terminate: it could kill the worker while it holds _sessionTargetsMutex
537 _shutdownWorkerThread(_workerThread, UDPLinkLog(), false /* allowTerminate */);
538
539 qCDebug(UDPLinkLog) << this;
540}
541
543{
544 return _worker && _worker->isConnected();
545}
546
548{
549 return QMetaObject::invokeMethod(_worker, "connectLink", Qt::QueuedConnection);
550}
551
553{
554 if (isConnected()) {
555 (void) QMetaObject::invokeMethod(_worker, "disconnectLink", Qt::QueuedConnection);
556 }
557}
558
559void UDPLink::_onConnected()
560{
561 _disconnectedEmitted = false;
562 emit connected();
563}
564
565void UDPLink::_onDisconnected()
566{
567 if (!_disconnectedEmitted.exchange(true)) {
568 emit disconnected();
569 }
570}
571
572void UDPLink::_onErrorOccurred(const QString &errorString)
573{
574 qCWarning(UDPLinkLog) << "Communication error:" << errorString;
575 emit communicationError(tr("UDP Link Error"), tr("Link %1: %2").arg(_udpConfig->name(), errorString));
576}
577
578void UDPLink::_onDataReceived(const QByteArray &data)
579{
580 emit bytesReceived(this, data);
581}
582
583void UDPLink::_onDataSent(const QByteArray &data)
584{
585 emit bytesSent(this, data);
586}
587
588void UDPLink::_writeBytes(const QByteArray& bytes)
589{
590 (void) QMetaObject::invokeMethod(_worker, "writeData", Qt::QueuedConnection, Q_ARG(QByteArray, bytes));
591}
592
Config config
std::shared_ptr< LinkConfiguration > SharedLinkConfigurationPtr
QString errorString
Error error
#define QGC_LOGGING_CATEGORY(name, categoryStr)
Interface holding link specific settings.
virtual void copyFrom(const LinkConfiguration *source)
QString name() const
The link interface defines the interface for all links used to communicate with the ground station ap...
void bytesReceived(LinkInterface *link, const QByteArray &data)
void disconnected()
void _shutdownWorkerThread(QThread *thread, const QLoggingCategory &category, bool allowTerminate=true)
void communicationError(const QString &title, const QString &error)
void bytesSent(LinkInterface *link, const QByteArray &data)
void connected()
AutoConnectSettings * autoConnectSettings() const
static SettingsManager * instance()
void saveSettings(QSettings &settings, const QString &root) const override
Definition UDPLink.cc:110
QStringList resolveHosts() const
Returns the hostnames that failed to resolve.
Definition UDPLink.cc:220
UDPConfiguration(const QString &name, QObject *parent=nullptr)
Definition UDPLink.cc:46
QList< std::shared_ptr< UDPClient > > targetHosts() const
Definition UDPLink.h:84
quint16 localPort() const
Definition UDPLink.h:94
Q_INVOKABLE void removeHost(const QString &host)
Definition UDPLink.cc:165
void hostListChanged()
void setLocalPort(quint16 port)
Definition UDPLink.h:95
Q_INVOKABLE void addHost(const QString &host)
Definition UDPLink.cc:129
virtual ~UDPConfiguration()
Definition UDPLink.cc:59
void copyFrom(const LinkConfiguration *source) override
Definition UDPLink.cc:66
void loadSettings(QSettings &settings, const QString &root) override
Definition UDPLink.cc:89
void setRequireResolvedHosts(bool require)
Definition UDPLink.h:92
bool requireResolvedHosts() const
When false, connect proceeds (listening) even if a target host fails to resolve.
Definition UDPLink.h:90
void dataReceived(const QByteArray &data)
void disconnected()
bool isConnected() const
Definition UDPLink.cc:280
void dataSent(const QByteArray &data)
void errorOccurred(const QString &errorString)
void writeData(const QByteArray &data)
Definition UDPLink.cc:383
void setupSocket()
Definition UDPLink.cc:286
virtual ~UDPWorker()
Definition UDPLink.cc:273
UDPWorker(const UDPConfiguration *config, QObject *parent=nullptr)
Definition UDPLink.cc:266
void connected()
void connectLink()
Definition UDPLink.cc:325
void disconnectLink()
Definition UDPLink.cc:368
bool isNetworkEthernet()
Check if current network connection is Ethernet.