Newer
Older
/****************************************************************************
*
* (c) 2009-2020 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.
*
****************************************************************************/
#include <QTimer>
#include <QList>
#include <QDebug>
#include <QMutexLocker>
Lorenz Meier
committed
#include <QNetworkInterface>
#include <QHostInfo>
dogmaphobic
committed
#include "QGCApplication.h"
#include "SettingsManager.h"
#include "AutoConnectSettings.h"
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())) {
}
static QString get_ip_address(const QString& address)
{
// Need to look it up
QHostInfo info = QHostInfo::fromName(address);
QList<QHostAddress> hostAddresses = info.addresses();
return hostAddresses.at(i).toString();
}
}
}
static bool contains_target(const QList<UDPCLient*> list, const QHostAddress& address, quint16 port)
for (int i=0; i<list.count(); i++) {
UDPCLient* target = list[i];
if (target->address == address && target->port == port) {
UDPLink::UDPLink(SharedLinkConfigurationPointer& config)
: LinkInterface (config)
#if defined(QGC_ZEROCONF_ENABLED)
, _dnssServiceRef (nullptr)
#endif
, _running (false)
, _socket (nullptr)
, _udpConfig (qobject_cast<UDPConfiguration*>(config.data()))
, _connectState (false)
if (!_udpConfig) {
qWarning() << "Internal error";
}
auto allAddresses = QNetworkInterface::allAddresses();
for (int i=0; i<allAddresses.count(); i++) {
QHostAddress &address = allAddresses[i];
_localAddresses.append(QHostAddress(address));
Gus Grubba
committed
}
Lorenz Meier
committed
moveToThread(this);
_running = false;
qDeleteAll(_sessionTargets);
_sessionTargets.clear();
dogmaphobic
committed
this->deleteLater();
}
/**
* @brief Runs the thread
*
**/
void UDPLink::run()
{
}
dogmaphobic
committed
void UDPLink::_restartConnection()
dogmaphobic
committed
_disconnect();
_connect();
}
dogmaphobic
committed
QString UDPLink::getName() const
return _udpConfig->name();
Gus Grubba
committed
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.
for (int i=0; i<_localAddresses.count(); i++) {
QHostAddress &address = _localAddresses[i];
Gus Grubba
committed
if (address == add) {
// This is a local address of the same host
return true;
}
}
return false;
}
void UDPLink::_writeBytes(const QByteArray data)
Gus Grubba
committed
if (!_socket) {
Gus Grubba
committed
}
for (int i=0; i<_udpConfig->targetHosts().count(); i++) {
UDPCLient* target = _udpConfig->targetHosts()[i];
if(!contains_target(_sessionTargets, target->address, target->port)) {
for(UDPCLient* target: _sessionTargets) {
_writeDataGram(data, target);
}
}
void UDPLink::_writeDataGram(const QByteArray data, const UDPCLient* target)
{
//qDebug() << "UDP Out" << target->address << target->port;
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());
}
/**
* @brief Read a number of bytes from the interface.
**/
void UDPLink::readBytes()
Gus Grubba
committed
if (!_socket) {
return;
}
QByteArray databuffer;
while (_socket->hasPendingDatagrams())
{
QByteArray datagram;
datagram.resize(_socket->pendingDatagramSize());
QHostAddress sender;
quint16 senderPort;
Gus Grubba
committed
//-- Note: This call is broken in Qt 5.9.3 on Windows. It always returns a blank sender and 0 for the port.
_socket->readDatagram(datagram.data(), datagram.size(), &sender, &senderPort);
databuffer.append(datagram);
//-- Wait a bit before sending it over
_logInputDataRate(datagram.length(), QDateTime::currentMSecsSinceEpoch());
// TODO: This doesn't validade the sender. Anything sending UDP packets to this port gets
dogmaphobic
committed
// 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
committed
if(_isIpLocal(sender)) {
QMutexLocker locker(&_sessionTargetsMutex);
if (!contains_target(_sessionTargets, asender, senderPort)) {
qDebug() << "Adding target" << asender << senderPort;
UDPCLient* target = new UDPCLient(asender, senderPort);
_sessionTargets.append(target);
}
}
/**
* @brief Disconnect the connection.
*
* @return True if connection has been disconnected, false if connection couldn't be disconnected.
**/
_running = false;
wait();
dogmaphobic
committed
if (_socket) {
// Make sure delete happen on correct thread
_socket->deleteLater();
dogmaphobic
committed
}
_connectState = false;
}
/**
* @brief Connect the connection.
*
* @return True if connection has been established, false if connection couldn't be established.
**/
bool UDPLink::_connect(void)
_running = false;
wait();
dogmaphobic
committed
}
_running = true;
dogmaphobic
committed
bool UDPLink::_hardwareConnect()
if (_socket) {
delete _socket;
QHostAddress host = QHostAddress::AnyIPv4;
_socket->setProxy(QNetworkProxy::NoProxy);
_connectState = _socket->bind(host, _udpConfig->localPort(), QAbstractSocket::ReuseAddressHint | QUdpSocket::ShareAddress);
dogmaphobic
committed
if (_connectState) {
_socket->joinMulticastGroup(QHostAddress("224.0.0.1"));
_socket->setSocketOption(QAbstractSocket::SendBufferSizeSocketOption, 64 * 1024);
_socket->setSocketOption(QAbstractSocket::ReceiveBufferSizeSocketOption, 128 * 1024);
_socket->setSocketOption(QAbstractSocket::SendBufferSizeSocketOption, 256 * 1024);
_socket->setSocketOption(QAbstractSocket::ReceiveBufferSizeSocketOption, 512 * 1024);
_registerZeroconf(_udpConfig->localPort(), kZeroconfRegistration);
QObject::connect(_socket, &QUdpSocket::readyRead, this, &UDPLink::readBytes);
emit connected();
emit communicationError(tr("UDP Link Error"), tr("Error binding UDP port: %1").arg(_socket->errorString()));
}
dogmaphobic
committed
return _connectState;
}
/**
* @brief Check if connection is active.
*
* @return True if link is connected, false otherwise.
**/
bool UDPLink::isConnected() const
dogmaphobic
committed
return _connectState;
dogmaphobic
committed
qint64 UDPLink::getConnectionSpeed() const
dogmaphobic
committed
return 54000000; // 54 Mbit
}
qint64 UDPLink::getCurrentInDataRate() const
{
return 0;
dogmaphobic
committed
qint64 UDPLink::getCurrentOutDataRate() const
dogmaphobic
committed
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(tr("UDP Link Error"), tr("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;
}
dogmaphobic
committed
//--------------------------------------------------------------------------
//-- 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().toUInt());
UDPConfiguration::UDPConfiguration(UDPConfiguration* source) : LinkConfiguration(source)
_copyFrom(source);
}
UDPConfiguration::~UDPConfiguration()
{
_clearTargetHosts();
dogmaphobic
committed
void UDPConfiguration::copyFrom(LinkConfiguration *source)
LinkConfiguration::copyFrom(source);
_copyFrom(source);
}
void UDPConfiguration::_copyFrom(LinkConfiguration *source)
{
auto* usource = qobject_cast<UDPConfiguration*>(source);
for (int i=0; i<usource->targetHosts().count(); i++) {
UDPCLient* target = usource->targetHosts()[i];
if(!contains_target(_targetHosts, target->address, target->port)) {
UDPCLient* newTarget = new UDPCLient(target);
_targetHosts.append(newTarget);
dogmaphobic
committed
}
}
qDeleteAll(_targetHosts);
_targetHosts.clear();
dogmaphobic
committed
/**
* @param host Hostname in standard formatt, e.g. localhost:14551 or 192.168.1.1:14551
*/
void UDPConfiguration::addHost(const QString host)
dogmaphobic
committed
{
addHost(host.split(":").first(), host.split(":").last().toUInt());
dogmaphobic
committed
}
}
void UDPConfiguration::addHost(const QString& host, quint16 port)
dogmaphobic
committed
{
qWarning() << "UDP:" << "Could not resolve host:" << host << "port:" << port;
if(!contains_target(_targetHosts, address, port)) {
UDPCLient* newTarget = new UDPCLient(address, port);
_targetHosts.append(newTarget);
_updateHostList();
dogmaphobic
committed
}
void UDPConfiguration::removeHost(const QString host)
dogmaphobic
committed
{
QHostAddress address = QHostAddress(get_ip_address(host.split(":").first()));
quint16 port = host.split(":").last().toUInt();
UDPCLient* target = _targetHosts.at(i);
if(target->address == address && target->port == port) {
_targetHosts.removeAt(i);
delete target;
_updateHostList();
return;
}
}
dogmaphobic
committed
}
qWarning() << "UDP:" << "Could not remove unknown host:" << host;
dogmaphobic
committed
}
void UDPConfiguration::setLocalPort(quint16 port)
{
_localPort = port;
}
void UDPConfiguration::saveSettings(QSettings& settings, const QString& root)
{
settings.beginGroup(root);
settings.setValue("port", (int)_localPort);
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);
dogmaphobic
committed
}
settings.endGroup();
}
void UDPConfiguration::loadSettings(QSettings& settings, const QString& root)
{
AutoConnectSettings* acSettings = qgcApp()->toolbox()->settingsManager()->autoConnectSettings();
_localPort = (quint16)settings.value("port", acSettings->udpListenPort()->rawValue().toInt()).toUInt();
dogmaphobic
committed
int hostCount = settings.value("hostCount", 0).toInt();
dogmaphobic
committed
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).toUInt());
dogmaphobic
committed
}
}
settings.endGroup();
dogmaphobic
committed
}
void UDPConfiguration::updateSettings()
{
dogmaphobic
committed
UDPLink* ulink = dynamic_cast<UDPLink*>(_link);
if(ulink) {
ulink->_restartConnection();
}
}
void UDPConfiguration::_updateHostList()
{
_hostList.clear();
UDPCLient* target = _targetHosts.at(i);
QString host = QString("%1").arg(target->address.toString()) + ":" + QString("%1").arg(target->port);
_hostList << host;
}
emit hostListChanged();
}