Merge pull request #20 from guh/fix-upnp

improve UPnP discovery
This commit is contained in:
Michael Zanetti 2018-06-05 20:31:22 +02:00 committed by GitHub
commit 7200af7f77
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 95 additions and 85 deletions

View File

@ -21,13 +21,10 @@ void NymeaDiscovery::setDiscovering(bool discovering)
if (m_discovering != discovering) { if (m_discovering != discovering) {
m_discovering = discovering; m_discovering = discovering;
// For zeroconf we'll ignore it as zeroconf doesn't do active discovery but just listens for changes in the net all the time // For zeroconf we'll ignore it as zeroconf doesn't do active discovery but just listens for changes in the net all the time
// If we don't have zeroconf available, start an active upnp discovery if (discovering) {
if (!m_zeroConf->available()) { m_upnp->discover();
if (discovering) { } else {
m_upnp->discover(); m_upnp->stopDiscovery();
} else {
m_upnp->stopDiscovery();
}
} }
emit discoveringChanged(); emit discoveringChanged();
} }

View File

@ -23,12 +23,11 @@
#include <QDebug> #include <QDebug>
#include <QUrl> #include <QUrl>
#include <QXmlStreamReader> #include <QXmlStreamReader>
#include <QNetworkInterface>
UpnpDiscovery::UpnpDiscovery(DiscoveryModel *discoveryModel, QObject *parent) : UpnpDiscovery::UpnpDiscovery(DiscoveryModel *discoveryModel, QObject *parent) :
QUdpSocket(parent), QObject(parent),
m_discoveryModel(discoveryModel), m_discoveryModel(discoveryModel)
m_discovering(false),
m_available(false)
{ {
m_networkAccessManager = new QNetworkAccessManager(this); m_networkAccessManager = new QNetworkAccessManager(this);
connect(m_networkAccessManager, &QNetworkAccessManager::finished, this, &UpnpDiscovery::networkReplyFinished); connect(m_networkAccessManager, &QNetworkAccessManager::finished, this, &UpnpDiscovery::networkReplyFinished);
@ -36,76 +35,65 @@ UpnpDiscovery::UpnpDiscovery(DiscoveryModel *discoveryModel, QObject *parent) :
m_repeatTimer.setInterval(500); m_repeatTimer.setInterval(500);
connect(&m_repeatTimer, &QTimer::timeout, this, &UpnpDiscovery::writeDiscoveryPacket); connect(&m_repeatTimer, &QTimer::timeout, this, &UpnpDiscovery::writeDiscoveryPacket);
// bind udp socket and join multicast group foreach (const QNetworkInterface &iface, QNetworkInterface::allInterfaces()) {
m_port = 1900; if (!iface.flags().testFlag(QNetworkInterface::CanMulticast)) {
m_host = QHostAddress("239.255.255.250"); continue;
}
setSocketOption(QAbstractSocket::MulticastTtlOption,QVariant(1)); foreach (const QNetworkAddressEntry &netAddressEntry, iface.addressEntries()) {
setSocketOption(QAbstractSocket::MulticastLoopbackOption,QVariant(1)); if (netAddressEntry.ip().protocol() == QAbstractSocket::IPv4Protocol) {
QUdpSocket *socket = new QUdpSocket(this);
if(!bind(QHostAddress::AnyIPv4, m_port, QUdpSocket::ShareAddress)){ int port = -1;
qWarning() << "UPnP discovery could not bind to port" << m_port; for (int i = 49125; i < 65535; i++) {
setAvailable(false); if(socket->bind(netAddressEntry.ip(), i, QUdpSocket::DontShareAddress)){
return; port = i;
break;
}
}
if (port == 65535 || socket->state() != QUdpSocket::BoundState) {
socket->deleteLater();
qWarning() << "UPnP discovery could not bind to interface" << netAddressEntry.ip();
continue;
}
qDebug() << "Discovering on" << netAddressEntry.ip() << port;
m_sockets.append(socket);
connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(error(QAbstractSocket::SocketError)));
connect(socket, &QUdpSocket::readyRead, this, &UpnpDiscovery::readData);
}
}
} }
if(!joinMulticastGroup(m_host)){
qWarning() << "UPnP discovery could not join multicast group" << m_host;
setAvailable(false);
return;
}
connect(this, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(error(QAbstractSocket::SocketError)));
connect(this, &UpnpDiscovery::readyRead, this, &UpnpDiscovery::readData);
setAvailable(true);
} }
bool UpnpDiscovery::discovering() const bool UpnpDiscovery::discovering() const
{ {
return m_discovering; return m_repeatTimer.isActive();
} }
bool UpnpDiscovery::available() const bool UpnpDiscovery::available() const
{ {
return m_available; return !m_sockets.isEmpty();
} }
void UpnpDiscovery::discover() void UpnpDiscovery::discover()
{ {
if (!m_available) { if (!available()) {
qWarning() << "Could not discover. UPnP not available."; qWarning() << "Could not discover. UPnP not available.";
return; return;
} }
qDebug() << "start discovering..."; qDebug() << "start discovering...";
m_repeatTimer.start(); m_repeatTimer.start();
// m_discoveryModel->clearModel();
m_foundDevices.clear(); m_foundDevices.clear();
setDiscovering(true);
writeDiscoveryPacket(); writeDiscoveryPacket();
emit discoveringChanged();
} }
void UpnpDiscovery::stopDiscovery() void UpnpDiscovery::stopDiscovery()
{ {
qDebug() << "stop discovering"; qDebug() << "stop discovering";
m_repeatTimer.stop(); m_repeatTimer.stop();
setDiscovering(false);
}
void UpnpDiscovery::setDiscovering(const bool &discovering)
{
m_discovering = discovering;
emit discoveringChanged(); emit discoveringChanged();
} }
void UpnpDiscovery::setAvailable(const bool &available)
{
m_available = available;
emit availableChanged();
}
void UpnpDiscovery::writeDiscoveryPacket() void UpnpDiscovery::writeDiscoveryPacket()
{ {
QByteArray ssdpSearchMessage = QByteArray("M-SEARCH * HTTP/1.1\r\n" QByteArray ssdpSearchMessage = QByteArray("M-SEARCH * HTTP/1.1\r\n"
@ -114,33 +102,43 @@ void UpnpDiscovery::writeDiscoveryPacket()
"MX:2\r\n" "MX:2\r\n"
"ST: ssdp:all\r\n\r\n"); "ST: ssdp:all\r\n\r\n");
// qDebug() << "sending discovery packet"; qDebug() << "sending discovery package";
writeDatagram(ssdpSearchMessage, m_host, m_port); foreach (QUdpSocket* socket, m_sockets) {
quint64 ret = socket->writeDatagram(ssdpSearchMessage, QHostAddress("239.255.255.250"), 1900);
if (ret != ssdpSearchMessage.length()) {
qWarning() << "Error sending SSDP query on socket" << socket->localAddress();
}
}
} }
void UpnpDiscovery::error(QAbstractSocket::SocketError error) void UpnpDiscovery::error(QAbstractSocket::SocketError error)
{ {
qWarning() << "UPnP socket error:" << error << errorString(); QUdpSocket* socket = static_cast<QUdpSocket*>(sender());
qWarning() << "UPnP socket error:" << error << socket->errorString();
} }
void UpnpDiscovery::readData() void UpnpDiscovery::readData()
{ {
QUdpSocket* socket = static_cast<QUdpSocket*>(sender());
QByteArray data; QByteArray data;
quint16 port; quint16 port;
QHostAddress hostAddress; QHostAddress hostAddress;
// read the answere from the multicast // read the answere from the multicast
while (hasPendingDatagrams()) { while (socket->hasPendingDatagrams()) {
data.resize(pendingDatagramSize()); data.resize(socket->pendingDatagramSize());
readDatagram(data.data(), data.size(), &hostAddress, &port); socket->readDatagram(data.data(), data.size(), &hostAddress, &port);
} }
if (!discovering()) { if (!discovering()) {
return; return;
} }
// qDebug() << "upnp packet" << data;
// if the data contains the HTTP OK header... // if the data contains the HTTP OK header...
if (data.contains("HTTP/1.1 200 OK") || data.contains("NOTIFY * HTTP/1.1")) { if (data.contains("HTTP/1.1 200 OK")) {
QUrl location; QUrl location;
bool isNymea = false; bool isNymea = false;
@ -206,42 +204,63 @@ void UpnpDiscovery::networkReplyFinished(QNetworkReply *reply)
if (xml.isStartDocument()) if (xml.isStartDocument())
continue; continue;
if (xml.isStartElement()) { if (xml.isStartElement()) {
// Check for old style websocketURL and nymeaRpcURL
if (xml.name().toString() == "websocketURL") { if (xml.name().toString() == "websocketURL") {
QUrl u(xml.readElementText()); QUrl u(xml.readElementText());
PortConfig *pc = new PortConfig(u.port()); PortConfig *pc = new PortConfig(u.port());
pc->setProtocol(PortConfig::ProtocolWebSocket); pc->setProtocol(PortConfig::ProtocolWebSocket);
pc->setSslEnabled(u.scheme().endsWith('s')); pc->setSslEnabled(u.scheme() == "wss");
portConfigList.append(pc); portConfigList.append(pc);
} }
}
if (xml.isStartElement()) {
if (xml.name().toString() == "nymeaRpcURL") { if (xml.name().toString() == "nymeaRpcURL") {
QUrl u(xml.readElementText()); QUrl u(xml.readElementText());
qDebug() << "have url" << u << u.scheme(); qDebug() << "have url" << u << u.scheme();
PortConfig *pc = new PortConfig(u.port()); PortConfig *pc = new PortConfig(u.port());
pc->setProtocol(PortConfig::ProtocolNymeaRpc); pc->setProtocol(PortConfig::ProtocolNymeaRpc);
pc->setSslEnabled(u.scheme().endsWith('s')); pc->setSslEnabled(u.scheme() == "nymeas");
portConfigList.append(pc); portConfigList.append(pc);
} }
}
if (xml.isStartElement()) { if (xml.name().toString() == "guhRpcURL") {
if (xml.name().toString() == "device") { QUrl u(xml.readElementText());
while (!xml.atEnd()) { qDebug() << "have url" << u << u.scheme();
if (xml.name() == "friendlyName" && xml.isStartElement()) { PortConfig *pc = new PortConfig(u.port());
name = xml.readElementText(); pc->setProtocol(PortConfig::ProtocolNymeaRpc);
} pc->setSslEnabled(u.scheme() == "guhs");
if (xml.name() == "modelNumber" && xml.isStartElement()) { portConfigList.append(pc);
version = xml.readElementText(); }
}
if (xml.name() == "UDN" && xml.isStartElement()) { // But also for new style serviceList
uuid = xml.readElementText().split(':').last(); if (xml.name().toString() == "serviceList") {
} while (!(xml.isEndElement() && xml.name().toString() == "serviceList") && !xml.atEnd()) {
xml.readNext(); xml.readNext();
if (xml.name().toString() == "service") {
while (!(xml.isEndElement() && xml.name().toString() == "service") && !xml.atEnd()) {
xml.readNext();
if (xml.name().toString() == "SCPDURL") {
QUrl u(xml.readElementText());
PortConfig *pc = new PortConfig(u.port());
pc->setProtocol(u.scheme().startsWith("nymea") ? PortConfig::ProtocolNymeaRpc : PortConfig::ProtocolWebSocket);
pc->setSslEnabled(u.scheme() == "nymeas" || u.scheme() == "wss");
portConfigList.append(pc);
}
}
}
} }
xml.readNext(); }
if (xml.name() == "friendlyName") {
name = xml.readElementText();
}
if (xml.name() == "modelNumber") {
version = xml.readElementText();
}
if (xml.name() == "UDN") {
uuid = xml.readElementText().split(':').last();
} }
} }
} }

View File

@ -30,7 +30,7 @@
#include "discoverydevice.h" #include "discoverydevice.h"
#include "discoverymodel.h" #include "discoverymodel.h"
class UpnpDiscovery : public QUdpSocket class UpnpDiscovery : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
@ -44,23 +44,16 @@ public:
Q_INVOKABLE void stopDiscovery(); Q_INVOKABLE void stopDiscovery();
private: private:
QList<QUdpSocket*> m_sockets;
QNetworkAccessManager *m_networkAccessManager; QNetworkAccessManager *m_networkAccessManager;
QTimer m_repeatTimer; QTimer m_repeatTimer;
QHostAddress m_host;
qint16 m_port;
DiscoveryModel *m_discoveryModel; DiscoveryModel *m_discoveryModel;
bool m_discovering;
bool m_available;
QHash<QNetworkReply *, QHostAddress> m_runningReplies; QHash<QNetworkReply *, QHostAddress> m_runningReplies;
QList<QUrl> m_foundDevices; QList<QUrl> m_foundDevices;
void setDiscovering(const bool &discovering);
void setAvailable(const bool &available);
signals: signals:
void discoveringChanged(); void discoveringChanged();
void availableChanged(); void availableChanged();

View File

@ -582,6 +582,7 @@ Page {
} }
onClicked: { onClicked: {
Engine.connection.connect(dialog.discoveryDevice.toUrl(index)) Engine.connection.connect(dialog.discoveryDevice.toUrl(index))
dialog.close()
} }
} }
} }