You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
549 lines
16 KiB
549 lines
16 KiB
/**************************************************************************** |
|
* |
|
* (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. |
|
* |
|
****************************************************************************/ |
|
|
|
|
|
/** |
|
* @file |
|
* @brief Definition of UDP connection (server) for unmanned vehicles |
|
* @author Lorenz Meier <mavteam@student.ethz.ch> |
|
* |
|
*/ |
|
|
|
#include <QtGlobal> |
|
#include <QTimer> |
|
#include <QList> |
|
#include <QDebug> |
|
#include <QMutexLocker> |
|
#include <QNetworkProxy> |
|
#include <QNetworkInterface> |
|
#include <iostream> |
|
#include <QHostInfo> |
|
|
|
#include "UDPLink.h" |
|
#include "QGC.h" |
|
#include "QGCApplication.h" |
|
#include "SettingsManager.h" |
|
#include "AutoConnectSettings.h" |
|
|
|
#define REMOVE_GONE_HOSTS 0 |
|
|
|
static const char* kZeroconfRegistration = "_qgroundcontrol._udp"; |
|
|
|
static bool is_ip(const QString& address) |
|
{ |
|
int a,b,c,d; |
|
if (sscanf(address.toStdString().c_str(), "%d.%d.%d.%d", &a, &b, &c, &d) != 4 |
|
&& strcmp("::1", address.toStdString().c_str())) { |
|
return false; |
|
} else { |
|
return true; |
|
} |
|
} |
|
|
|
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(""); |
|
} |
|
|
|
UDPLink::UDPLink(SharedLinkConfigurationPointer& config) |
|
: LinkInterface(config) |
|
#if defined(QGC_ZEROCONF_ENABLED) |
|
, _dnssServiceRef(NULL) |
|
#endif |
|
, _running(false) |
|
, _socket(NULL) |
|
, _udpConfig(qobject_cast<UDPConfiguration*>(config.data())) |
|
, _connectState(false) |
|
{ |
|
if (!_udpConfig) { |
|
qWarning() << "Internal error"; |
|
} |
|
moveToThread(this); |
|
} |
|
|
|
UDPLink::~UDPLink() |
|
{ |
|
_disconnect(); |
|
// Tell the thread to exit |
|
_running = false; |
|
quit(); |
|
// Wait for it to exit |
|
wait(); |
|
this->deleteLater(); |
|
} |
|
|
|
/** |
|
* @brief Runs the thread |
|
* |
|
**/ |
|
void UDPLink::run() |
|
{ |
|
if(_hardwareConnect()) { |
|
exec(); |
|
} |
|
if (_socket) { |
|
_deregisterZeroconf(); |
|
_socket->close(); |
|
} |
|
} |
|
|
|
void UDPLink::_restartConnection() |
|
{ |
|
if(this->isConnected()) |
|
{ |
|
_disconnect(); |
|
_connect(); |
|
} |
|
} |
|
|
|
QString UDPLink::getName() const |
|
{ |
|
return _udpConfig->name(); |
|
} |
|
|
|
void UDPLink::addHost(const QString& host) |
|
{ |
|
_udpConfig->addHost(host); |
|
} |
|
|
|
void UDPLink::removeHost(const QString& host) |
|
{ |
|
_udpConfig->removeHost(host); |
|
} |
|
|
|
void UDPLink::_writeBytes(const QByteArray data) |
|
{ |
|
if (!_socket) |
|
return; |
|
|
|
QStringList goneHosts; |
|
// Send to all connected systems |
|
QString host; |
|
int port; |
|
if(_udpConfig->firstHost(host, port)) { |
|
do { |
|
QHostAddress currentHost(host); |
|
if(_socket->writeDatagram(data, currentHost, (quint16)port) < 0) { |
|
// This host is gone. Add to list to be removed |
|
// 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. |
|
_logOutputDataRate(data.size(), QDateTime::currentMSecsSinceEpoch()); |
|
} |
|
} while (_udpConfig->nextHost(host, port)); |
|
//-- Remove hosts that are no longer there |
|
foreach (const QString& ghost, goneHosts) { |
|
_udpConfig->removeHost(ghost); |
|
} |
|
} |
|
} |
|
|
|
/** |
|
* @brief Read a number of bytes from the interface. |
|
**/ |
|
void UDPLink::readBytes() |
|
{ |
|
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) { |
|
emit bytesReceived(this, databuffer); |
|
databuffer.clear(); |
|
} |
|
_logInputDataRate(datagram.length(), QDateTime::currentMSecsSinceEpoch()); |
|
// 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 |
|
_udpConfig->addHost(sender.toString(), (int)senderPort); |
|
} |
|
//-- Send whatever is left |
|
if(databuffer.size()) { |
|
emit bytesReceived(this, databuffer); |
|
} |
|
} |
|
|
|
/** |
|
* @brief Disconnect the connection. |
|
* |
|
* @return True if connection has been disconnected, false if connection couldn't be disconnected. |
|
**/ |
|
void UDPLink::_disconnect(void) |
|
{ |
|
_running = false; |
|
quit(); |
|
wait(); |
|
if (_socket) { |
|
// Make sure delete happen on correct thread |
|
_socket->deleteLater(); |
|
_socket = NULL; |
|
emit disconnected(); |
|
} |
|
_connectState = false; |
|
} |
|
|
|
/** |
|
* @brief Connect the connection. |
|
* |
|
* @return True if connection has been established, false if connection couldn't be established. |
|
**/ |
|
bool UDPLink::_connect(void) |
|
{ |
|
if(this->isRunning() || _running) |
|
{ |
|
_running = false; |
|
quit(); |
|
wait(); |
|
} |
|
_running = true; |
|
start(NormalPriority); |
|
return true; |
|
} |
|
|
|
bool UDPLink::_hardwareConnect() |
|
{ |
|
if (_socket) { |
|
delete _socket; |
|
_socket = NULL; |
|
} |
|
QHostAddress host = QHostAddress::AnyIPv4; |
|
_socket = new QUdpSocket(); |
|
_socket->setProxy(QNetworkProxy::NoProxy); |
|
_connectState = _socket->bind(host, _udpConfig->localPort(), QAbstractSocket::ReuseAddressHint | QUdpSocket::ShareAddress); |
|
if (_connectState) { |
|
_socket->joinMulticastGroup(QHostAddress("224.0.0.1")); |
|
//-- Make sure we have a large enough IO buffers |
|
#ifdef __mobile__ |
|
_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 |
|
_registerZeroconf(_udpConfig->localPort(), kZeroconfRegistration); |
|
QObject::connect(_socket, &QUdpSocket::readyRead, this, &UDPLink::readBytes); |
|
emit connected(); |
|
} else { |
|
emit communicationError(tr("UDP Link Error"), tr("Error binding UDP port: %1").arg(_socket->errorString())); |
|
} |
|
return _connectState; |
|
} |
|
|
|
/** |
|
* @brief Check if connection is active. |
|
* |
|
* @return True if link is connected, false otherwise. |
|
**/ |
|
bool UDPLink::isConnected() const |
|
{ |
|
return _connectState; |
|
} |
|
|
|
qint64 UDPLink::getConnectionSpeed() const |
|
{ |
|
return 54000000; // 54 Mbit |
|
} |
|
|
|
qint64 UDPLink::getCurrentInDataRate() const |
|
{ |
|
return 0; |
|
} |
|
|
|
qint64 UDPLink::getCurrentOutDataRate() const |
|
{ |
|
return 0; |
|
} |
|
|
|
void UDPLink::_registerZeroconf(uint16_t port, const std::string ®Type) |
|
{ |
|
#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 |
|
} |
|
|
|
//-------------------------------------------------------------------------- |
|
//-- UDPConfiguration |
|
|
|
UDPConfiguration::UDPConfiguration(const QString& name) : LinkConfiguration(name) |
|
{ |
|
AutoConnectSettings* settings = qgcApp()->toolbox()->settingsManager()->autoConnectSettings(); |
|
_localPort = settings->udpListenPort()->rawValue().toInt(); |
|
QString targetHostIP = settings->udpTargetHostIP()->rawValue().toString(); |
|
if (!targetHostIP.isEmpty()) { |
|
addHost(targetHostIP, settings->udpTargetHostPort()->rawValue().toInt()); |
|
} |
|
} |
|
|
|
UDPConfiguration::UDPConfiguration(UDPConfiguration* source) : LinkConfiguration(source) |
|
{ |
|
_localPort = source->localPort(); |
|
QString host; |
|
int port; |
|
_hostList.clear(); |
|
if(source->firstHost(host, port)) { |
|
do { |
|
addHost(host, port); |
|
} while(source->nextHost(host, port)); |
|
} |
|
} |
|
|
|
void UDPConfiguration::copyFrom(LinkConfiguration *source) |
|
{ |
|
LinkConfiguration::copyFrom(source); |
|
UDPConfiguration* usource = dynamic_cast<UDPConfiguration*>(source); |
|
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"; |
|
} |
|
} |
|
|
|
/** |
|
* @param host Hostname in standard formatt, e.g. localhost:14551 or 192.168.1.1:14551 |
|
*/ |
|
void UDPConfiguration::addHost(const QString host) |
|
{ |
|
// Handle x.x.x.x:p |
|
if (host.contains(":")) |
|
{ |
|
addHost(host.split(":").first(), host.split(":").last().toInt()); |
|
} |
|
// If no port, use default |
|
else |
|
{ |
|
addHost(host, (int)_localPort); |
|
} |
|
} |
|
|
|
void UDPConfiguration::addHost(const QString& host, int port) |
|
{ |
|
bool changed = false; |
|
QMutexLocker locker(&_confMutex); |
|
if(_hosts.contains(host)) { |
|
if(_hosts[host] != port) { |
|
_hosts[host] = port; |
|
changed = true; |
|
} |
|
} else { |
|
QString ipAdd = get_ip_address(host); |
|
if(ipAdd.isEmpty()) { |
|
qWarning() << "UDP:" << "Could not resolve host:" << host << "port:" << port; |
|
} else { |
|
// 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; |
|
} |
|
changed = true; |
|
} |
|
} |
|
if(changed) { |
|
_updateHostList(); |
|
} |
|
} |
|
|
|
void UDPConfiguration::removeHost(const QString host) |
|
{ |
|
QMutexLocker locker(&_confMutex); |
|
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()) { |
|
//qDebug() << "UDP:" << "Removed host:" << host; |
|
_hosts.erase(i); |
|
} else { |
|
qWarning() << "UDP:" << "Could not remove unknown host:" << host; |
|
} |
|
_updateHostList(); |
|
} |
|
|
|
bool UDPConfiguration::firstHost(QString& host, int& port) |
|
{ |
|
_confMutex.lock(); |
|
_it = _hosts.begin(); |
|
if(_it == _hosts.end()) { |
|
_confMutex.unlock(); |
|
return false; |
|
} |
|
_confMutex.unlock(); |
|
return nextHost(host, port); |
|
} |
|
|
|
bool UDPConfiguration::nextHost(QString& host, int& port) |
|
{ |
|
QMutexLocker locker(&_confMutex); |
|
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) |
|
{ |
|
AutoConnectSettings* acSettings = qgcApp()->toolbox()->settingsManager()->autoConnectSettings(); |
|
|
|
_confMutex.lock(); |
|
_hosts.clear(); |
|
_confMutex.unlock(); |
|
settings.beginGroup(root); |
|
_localPort = (quint16)settings.value("port", acSettings->udpListenPort()->rawValue().toInt()).toUInt(); |
|
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(); |
|
_updateHostList(); |
|
} |
|
|
|
void UDPConfiguration::updateSettings() |
|
{ |
|
if(_link) { |
|
UDPLink* ulink = dynamic_cast<UDPLink*>(_link); |
|
if(ulink) { |
|
ulink->_restartConnection(); |
|
} |
|
} |
|
} |
|
|
|
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(); |
|
}
|
|
|