diff --git a/app/app.pro b/app/app.pro index bf43427d..174a43d2 100644 --- a/app/app.pro +++ b/app/app.pro @@ -118,6 +118,7 @@ macx { } SOURCES += \ + backend/nvaddress.cpp \ backend/nvapp.cpp \ main.cpp \ backend/computerseeker.cpp \ @@ -156,6 +157,7 @@ SOURCES += \ wm.cpp HEADERS += \ + backend/nvaddress.h \ backend/nvapp.h \ settings/compatfetcher.h \ settings/mappingfetcher.h \ diff --git a/app/backend/computermanager.cpp b/app/backend/computermanager.cpp index 492955f9..dfe985fc 100644 --- a/app/backend/computermanager.cpp +++ b/app/backend/computermanager.cpp @@ -27,7 +27,7 @@ public: } private: - bool tryPollComputer(QString address, bool& changed) + bool tryPollComputer(NvAddress address, bool& changed) { NvHTTP http(address, m_Computer->serverCert); @@ -52,8 +52,6 @@ private: bool updateAppList(bool& changed) { - Q_ASSERT(m_Computer->activeAddress != nullptr); - NvHTTP http(m_Computer); QVector appList; @@ -88,7 +86,7 @@ private: if (tryPollComputer(address, stateChanged)) { if (!wasOnline) { - qInfo() << m_Computer->name << "is now online at" << m_Computer->activeAddress; + qInfo() << m_Computer->name << "is now online at" << m_Computer->activeAddress.toString(); } online = true; break; @@ -326,7 +324,7 @@ void ComputerManager::handleMdnsServiceResolved(MdnsPendingComputer* computer, // address may not be reachable (if the user hasn't installed the IPv6 helper yet // or if this host lacks outbound IPv6 capability). We want to add IPv6 even if // it's not currently reachable. - addNewHost(address.toString(), true, v6Global); + addNewHost(NvAddress(address, computer->port()), true, NvAddress(v6Global, computer->port())); added = true; break; } @@ -340,7 +338,7 @@ void ComputerManager::handleMdnsServiceResolved(MdnsPendingComputer* computer, if (address.isInSubnet(QHostAddress("fe80::"), 10) || address.isInSubnet(QHostAddress("fec0::"), 10) || address.isInSubnet(QHostAddress("fc00::"), 7)) { - addNewHost(address.toString(), true, v6Global); + addNewHost(NvAddress(address, computer->port()), true, NvAddress(v6Global, computer->port())); break; } } @@ -592,12 +590,24 @@ void ComputerManager::stopPollingAsync() } } +void ComputerManager::addNewHostManually(QString address) +{ + QUrl url = QUrl::fromUserInput(address); + if (!url.isEmpty() && url.isValid()) { + // If there wasn't a port specified, use the default + addNewHost(NvAddress(url.host(), url.port(47989)), false); + } + else { + emit computerAddCompleted(false, false); + } +} + class PendingAddTask : public QObject, public QRunnable { Q_OBJECT public: - PendingAddTask(ComputerManager* computerManager, QString address, QHostAddress mdnsIpv6Address, bool mdns) + PendingAddTask(ComputerManager* computerManager, NvAddress address, NvAddress mdnsIpv6Address, bool mdns) : m_ComputerManager(computerManager), m_Address(address), m_MdnsIpv6Address(mdnsIpv6Address), @@ -663,13 +673,13 @@ private: { NvHTTP http(m_Address, QSslCertificate()); - qInfo() << "Processing new PC at" << m_Address << "from" << (m_Mdns ? "mDNS" : "user") << m_MdnsIpv6Address; + qInfo() << "Processing new PC at" << m_Address.toString() << "from" << (m_Mdns ? "mDNS" : "user") << "with IPv6 address" << m_MdnsIpv6Address.toString(); // Perform initial serverinfo fetch over HTTP since we don't know which cert to use QString serverInfo = fetchServerInfo(http); if (serverInfo.isEmpty() && !m_MdnsIpv6Address.isNull()) { // Retry using the global IPv6 address if the IPv4 or link-local IPv6 address fails - http.setAddress(m_MdnsIpv6Address.toString()); + http.setAddress(m_MdnsIpv6Address); serverInfo = fetchServerInfo(http); } if (serverInfo.isEmpty()) { @@ -711,11 +721,11 @@ private: } // Get the WAN IP address using STUN if we're on mDNS over IPv4 - if (QHostAddress(newComputer->localAddress).protocol() == QAbstractSocket::IPv4Protocol) { + if (QHostAddress(newComputer->localAddress.address()).protocol() == QAbstractSocket::IPv4Protocol) { quint32 addr; int err = LiFindExternalAddressIP4("stun.moonlight-stream.org", 3478, &addr); if (err == 0) { - newComputer->remoteAddress = QHostAddress(qFromBigEndian(addr)).toString(); + newComputer->setRemoteAddress(QHostAddress(qFromBigEndian(addr))); } else { qWarning() << "STUN failed to get WAN address:" << err; @@ -723,15 +733,15 @@ private: } if (!m_MdnsIpv6Address.isNull()) { - Q_ASSERT(m_MdnsIpv6Address.protocol() == QAbstractSocket::IPv6Protocol); - newComputer->ipv6Address = m_MdnsIpv6Address.toString(); + Q_ASSERT(QHostAddress(m_MdnsIpv6Address.address()).protocol() == QAbstractSocket::IPv6Protocol); + newComputer->ipv6Address = m_MdnsIpv6Address; } } else { newComputer->manualAddress = m_Address; } - QHostAddress hostAddress(m_Address); + QHostAddress hostAddress(m_Address.address()); bool addressIsSiteLocalV4 = hostAddress.isInSubnet(QHostAddress("10.0.0.0"), 8) || hostAddress.isInSubnet(QHostAddress("172.16.0.0"), 12) || @@ -756,7 +766,7 @@ private: // Tell our client if something changed if (changed) { - qInfo() << existingComputer->name << "is now at" << existingComputer->activeAddress; + qInfo() << existingComputer->name << "is now at" << existingComputer->activeAddress.toString(); emit computerStateChanged(existingComputer); } } @@ -777,7 +787,7 @@ private: int err = LiFindExternalAddressIP4("stun.moonlight-stream.org", 3478, &addr); if (err == 0) { lock.relock(); - newComputer->remoteAddress = QHostAddress(qFromBigEndian(addr)).toString(); + newComputer->setRemoteAddress(QHostAddress(qFromBigEndian(addr))); lock.unlock(); } else { @@ -797,12 +807,12 @@ private: } ComputerManager* m_ComputerManager; - QString m_Address; - QHostAddress m_MdnsIpv6Address; + NvAddress m_Address; + NvAddress m_MdnsIpv6Address; bool m_Mdns; }; -void ComputerManager::addNewHost(QString address, bool mdns, QHostAddress mdnsIpv6Address) +void ComputerManager::addNewHost(NvAddress address, bool mdns, NvAddress mdnsIpv6Address) { // Punt to a worker thread to avoid stalling the // UI while waiting for serverinfo query to complete diff --git a/app/backend/computermanager.h b/app/backend/computermanager.h index 45f6d2f9..2c38237e 100644 --- a/app/backend/computermanager.h +++ b/app/backend/computermanager.h @@ -24,6 +24,7 @@ public: explicit MdnsPendingComputer(QMdnsEngine::Server* server, const QMdnsEngine::Service& service) : m_Hostname(service.hostname()), + m_Port(service.port()), m_Server(server), m_Resolver(nullptr) { @@ -41,6 +42,11 @@ public: return m_Hostname; } + uint16_t port() + { + return m_Port; + } + private slots: void handleResolvedTimeout() { @@ -75,6 +81,7 @@ private: } QByteArray m_Hostname; + uint16_t m_Port; QMdnsEngine::Server* m_Server; QMdnsEngine::Resolver* m_Resolver; QVector m_Addresses; @@ -169,7 +176,9 @@ public: Q_INVOKABLE void stopPollingAsync(); - Q_INVOKABLE void addNewHost(QString address, bool mdns, QHostAddress mdnsIpv6Address = QHostAddress()); + Q_INVOKABLE void addNewHostManually(QString address); + + void addNewHost(NvAddress address, bool mdns, NvAddress mdnsIpv6Address = NvAddress()); void pairHost(NvComputer* computer, QString pin); diff --git a/app/backend/computerseeker.cpp b/app/backend/computerseeker.cpp index c7674a5a..1a42ab8a 100644 --- a/app/backend/computerseeker.cpp +++ b/app/backend/computerseeker.cpp @@ -20,7 +20,7 @@ void ComputerSeeker::start(int timeout) // if m_ComputerName is UUID, or the name that doesn't resolve to an IP // address) and by polling it using mDNS, hopefully one of these methods // would find the host - m_ComputerManager->addNewHost(m_ComputerName, false); + m_ComputerManager->addNewHostManually(m_ComputerName); m_ComputerManager->startPolling(); } @@ -44,8 +44,8 @@ bool ComputerSeeker::matchComputer(NvComputer *computer) const return true; } - for (const QString& addr : computer->uniqueAddresses()) { - if (addr.toLower() == value) { + for (const NvAddress& addr : computer->uniqueAddresses()) { + if (addr.address().toLower() == value || addr.toString().toLower() == value) { return true; } } diff --git a/app/backend/nvcomputer.cpp b/app/backend/nvcomputer.cpp index 492336e4..94efaeda 100644 --- a/app/backend/nvcomputer.cpp +++ b/app/backend/nvcomputer.cpp @@ -7,13 +7,19 @@ #include #include +#define DEFAULT_HTTP_PORT 47989 + #define SER_NAME "hostname" #define SER_UUID "uuid" #define SER_MAC "mac" #define SER_LOCALADDR "localaddress" +#define SER_LOCALPORT "localport" #define SER_REMOTEADDR "remoteaddress" +#define SER_REMOTEPORT "remoteport" #define SER_MANUALADDR "manualaddress" +#define SER_MANUALPORT "manualport" #define SER_IPV6ADDR "ipv6address" +#define SER_IPV6PORT "ipv6port" #define SER_APPLIST "apps" #define SER_SRVCERT "srvcert" #define SER_CUSTOMNAME "customname" @@ -24,10 +30,14 @@ NvComputer::NvComputer(QSettings& settings) this->uuid = settings.value(SER_UUID).toString(); this->hasCustomName = settings.value(SER_CUSTOMNAME).toBool(); this->macAddress = settings.value(SER_MAC).toByteArray(); - this->localAddress = settings.value(SER_LOCALADDR).toString(); - this->remoteAddress = settings.value(SER_REMOTEADDR).toString(); - this->ipv6Address = settings.value(SER_IPV6ADDR).toString(); - this->manualAddress = settings.value(SER_MANUALADDR).toString(); + this->localAddress = NvAddress(settings.value(SER_LOCALADDR).toString(), + settings.value(SER_LOCALPORT, QVariant(DEFAULT_HTTP_PORT)).toUInt()); + this->remoteAddress = NvAddress(settings.value(SER_REMOTEADDR).toString(), + settings.value(SER_REMOTEPORT, QVariant(DEFAULT_HTTP_PORT)).toUInt()); + this->ipv6Address = NvAddress(settings.value(SER_IPV6ADDR).toString(), + settings.value(SER_IPV6PORT, QVariant(DEFAULT_HTTP_PORT)).toUInt()); + this->manualAddress = NvAddress(settings.value(SER_MANUALADDR).toString(), + settings.value(SER_MANUALPORT, QVariant(DEFAULT_HTTP_PORT)).toUInt()); this->serverCert = QSslCertificate(settings.value(SER_SRVCERT).toByteArray()); int appCount = settings.beginReadArray(SER_APPLIST); @@ -41,7 +51,6 @@ NvComputer::NvComputer(QSettings& settings) settings.endArray(); sortAppList(); - this->activeAddress = nullptr; this->currentGameId = 0; this->pairState = PS_UNKNOWN; this->state = CS_UNKNOWN; @@ -52,6 +61,14 @@ NvComputer::NvComputer(QSettings& settings) this->pendingQuit = false; this->gpuModel = nullptr; this->isSupportedServerVersion = true; + this->externalPort = this->remoteAddress.port(); +} + +void NvComputer::setRemoteAddress(QHostAddress address) +{ + Q_ASSERT(this->externalPort != 0); + + this->remoteAddress = NvAddress(address, this->externalPort); } void NvComputer::serialize(QSettings& settings) const @@ -62,10 +79,14 @@ void NvComputer::serialize(QSettings& settings) const settings.setValue(SER_CUSTOMNAME, hasCustomName); settings.setValue(SER_UUID, uuid); settings.setValue(SER_MAC, macAddress); - settings.setValue(SER_LOCALADDR, localAddress); - settings.setValue(SER_REMOTEADDR, remoteAddress); - settings.setValue(SER_IPV6ADDR, ipv6Address); - settings.setValue(SER_MANUALADDR, manualAddress); + settings.setValue(SER_LOCALADDR, localAddress.address()); + settings.setValue(SER_LOCALPORT, localAddress.port()); + settings.setValue(SER_REMOTEADDR, remoteAddress.address()); + settings.setValue(SER_REMOTEPORT, remoteAddress.port()); + settings.setValue(SER_IPV6ADDR, ipv6Address.address()); + settings.setValue(SER_IPV6PORT, ipv6Address.port()); + settings.setValue(SER_MANUALADDR, manualAddress.address()); + settings.setValue(SER_MANUALPORT, manualAddress.port()); settings.setValue(SER_SRVCERT, serverCert.toPem()); // Avoid deleting an existing applist if we couldn't get one @@ -130,12 +151,26 @@ NvComputer::NvComputer(NvHTTP& http, QString serverInfo) }); // We can get an IPv4 loopback address if we're using the GS IPv6 Forwarder - this->localAddress = NvHTTP::getXmlString(serverInfo, "LocalIP"); - if (this->localAddress.startsWith("127.")) { - this->localAddress = QString(); + this->localAddress = NvAddress(NvHTTP::getXmlString(serverInfo, "LocalIP"), http.httpPort()); + if (this->localAddress.address().startsWith("127.")) { + this->localAddress = NvAddress(); + } + + // This is an extension which is not present in GFE. It is present for Sunshine to be able + // to support dynamic HTTP WAN ports without requiring the user to manually enter the port. + QString remotePortStr = NvHTTP::getXmlString(serverInfo, "ExternalPort"); + if (remotePortStr.isEmpty() || (this->externalPort = this->remoteAddress.port()) == 0) { + this->externalPort = DEFAULT_HTTP_PORT; + } + + QString remoteAddress = NvHTTP::getXmlString(serverInfo, "ExternalIP"); + if (!remoteAddress.isEmpty()) { + this->remoteAddress = NvAddress(remoteAddress, this->externalPort); + } + else { + this->remoteAddress = NvAddress(); } - this->remoteAddress = NvHTTP::getXmlString(serverInfo, "ExternalIP"); this->pairState = NvHTTP::getXmlString(serverInfo, "PairStatus") == "1" ? PS_PAIRED : PS_NOT_PAIRED; this->currentGameId = NvHTTP::getCurrentGame(serverInfo); @@ -177,7 +212,10 @@ bool NvComputer::wake() // Add the addresses that we know this host to be // and broadcast addresses for this link just in // case the host has timed out in ARP entries. - QVector addressList = uniqueAddresses(); + QVector addressList; + for (const NvAddress& addr : uniqueAddresses()) { + addressList.append(addr.address()); + } addressList.append("255.255.255.255"); // Try to broadcast on all available NICs @@ -238,14 +276,14 @@ bool NvComputer::wake() bool NvComputer::isReachableOverVpn() { - if (activeAddress.isEmpty()) { + if (activeAddress.isNull()) { return false; } QTcpSocket s; s.setProxy(QNetworkProxy::NoProxy); - s.connectToHost(activeAddress, 47984); + s.connectToHost(activeAddress.address(), activeAddress.port()); if (s.waitForConnected(3000)) { Q_ASSERT(!s.localAddress().isNull()); @@ -331,9 +369,9 @@ bool NvComputer::updateAppList(QVector newAppList) { return true; } -QVector NvComputer::uniqueAddresses() const +QVector NvComputer::uniqueAddresses() const { - QVector uniqueAddressList; + QVector uniqueAddressList; // Start with addresses correctly ordered uniqueAddressList.append(activeAddress); @@ -344,7 +382,7 @@ QVector NvComputer::uniqueAddresses() const // Prune duplicates (always giving precedence to the first) for (int i = 0; i < uniqueAddressList.count(); i++) { - if (uniqueAddressList[i].isEmpty()) { + if (uniqueAddressList[i].isNull()) { uniqueAddressList.remove(i); i--; continue; @@ -400,10 +438,10 @@ bool NvComputer::update(NvComputer& that) ASSIGN_IF_CHANGED(name); } ASSIGN_IF_CHANGED_AND_NONEMPTY(macAddress); - ASSIGN_IF_CHANGED_AND_NONEMPTY(localAddress); - ASSIGN_IF_CHANGED_AND_NONEMPTY(remoteAddress); - ASSIGN_IF_CHANGED_AND_NONEMPTY(ipv6Address); - ASSIGN_IF_CHANGED_AND_NONEMPTY(manualAddress); + ASSIGN_IF_CHANGED_AND_NONNULL(localAddress); + ASSIGN_IF_CHANGED_AND_NONNULL(remoteAddress); + ASSIGN_IF_CHANGED_AND_NONNULL(ipv6Address); + ASSIGN_IF_CHANGED_AND_NONNULL(manualAddress); ASSIGN_IF_CHANGED(pairState); ASSIGN_IF_CHANGED(serverCodecModeSupport); ASSIGN_IF_CHANGED(currentGameId); diff --git a/app/backend/nvcomputer.h b/app/backend/nvcomputer.h index a170720b..8f4e1b02 100644 --- a/app/backend/nvcomputer.h +++ b/app/backend/nvcomputer.h @@ -1,6 +1,7 @@ #pragma once #include "nvhttp.h" +#include "nvaddress.h" #include #include @@ -25,6 +26,9 @@ public: explicit NvComputer(QSettings& settings); + void + setRemoteAddress(QHostAddress); + bool update(NvComputer& that); @@ -34,7 +38,7 @@ public: bool isReachableOverVpn(); - QVector + QVector uniqueAddresses() const; void @@ -57,7 +61,7 @@ public: // Ephemeral traits ComputerState state; PairState pairState; - QString activeAddress; + NvAddress activeAddress; int currentGameId; QString gfeVersion; QString appVersion; @@ -68,10 +72,10 @@ public: bool isSupportedServerVersion; // Persisted traits - QString localAddress; - QString remoteAddress; - QString ipv6Address; - QString manualAddress; + NvAddress localAddress; + NvAddress remoteAddress; + NvAddress ipv6Address; + NvAddress manualAddress; QByteArray macAddress; QString name; bool hasCustomName; @@ -81,4 +85,7 @@ public: // Synchronization mutable QReadWriteLock lock; + +private: + uint16_t externalPort; }; diff --git a/app/backend/nvhttp.cpp b/app/backend/nvhttp.cpp index a542dd21..b578e8b0 100644 --- a/app/backend/nvhttp.cpp +++ b/app/backend/nvhttp.cpp @@ -18,16 +18,17 @@ #define RESUME_TIMEOUT_MS 30000 #define QUIT_TIMEOUT_MS 30000 -NvHTTP::NvHTTP(QString address, QSslCertificate serverCert) : +NvHTTP::NvHTTP(NvAddress address, QSslCertificate serverCert) : m_ServerCert(serverCert) { m_BaseUrlHttp.setScheme("http"); m_BaseUrlHttps.setScheme("https"); - m_BaseUrlHttp.setPort(47989); - m_BaseUrlHttps.setPort(47984); setAddress(address); + // TODO: Use HttpsPort + setHttpsPort(47984); + // Never use a proxy server QNetworkProxy noProxy(QNetworkProxy::NoProxy); m_Nam.setProxy(noProxy); @@ -46,17 +47,24 @@ void NvHTTP::setServerCert(QSslCertificate serverCert) m_ServerCert = serverCert; } -void NvHTTP::setAddress(QString address) +void NvHTTP::setAddress(NvAddress address) { - Q_ASSERT(!address.isEmpty()); + Q_ASSERT(!address.isNull()); m_Address = address; - m_BaseUrlHttp.setHost(address); - m_BaseUrlHttps.setHost(address); + m_BaseUrlHttp.setHost(address.address()); + m_BaseUrlHttps.setHost(address.address()); + + m_BaseUrlHttp.setPort(address.port()); } -QString NvHTTP::address() +void NvHTTP::setHttpsPort(uint16_t port) +{ + m_BaseUrlHttps.setPort(port); +} + +NvAddress NvHTTP::address() { return m_Address; } @@ -362,6 +370,8 @@ NvHTTP::verifyResponseStatus(QString xml) } } } + + throw GfeHttpResponseException(-1, "Malformed GFE XML (missing root element)"); } QImage diff --git a/app/backend/nvhttp.h b/app/backend/nvhttp.h index 57ee7fe2..a08657a9 100644 --- a/app/backend/nvhttp.h +++ b/app/backend/nvhttp.h @@ -2,6 +2,7 @@ #include "identitymanager.h" #include "nvapp.h" +#include "nvaddress.h" #include @@ -108,7 +109,7 @@ public: NVLL_VERBOSE }; - explicit NvHTTP(QString address, QSslCertificate serverCert); + explicit NvHTTP(NvAddress address, QSslCertificate serverCert); explicit NvHTTP(NvComputer* computer); @@ -142,9 +143,10 @@ public: void setServerCert(QSslCertificate serverCert); - void setAddress(QString address); + void setAddress(NvAddress address); + void setHttpsPort(uint16_t port); - QString address(); + NvAddress address(); QSslCertificate serverCert(); @@ -194,7 +196,7 @@ private: int timeoutMs, NvLogLevel logLevel); - QString m_Address; + NvAddress m_Address; QNetworkAccessManager m_Nam; QSslCertificate m_ServerCert; }; diff --git a/app/gui/main.qml b/app/gui/main.qml index bad73e7b..e9483717 100644 --- a/app/gui/main.qml +++ b/app/gui/main.qml @@ -493,7 +493,7 @@ ApplicationWindow { onAccepted: { if (editText.text) { - ComputerManager.addNewHost(editText.text.trim(), false) + ComputerManager.addNewHostManually(editText.text.trim()) } } diff --git a/app/streaming/session.cpp b/app/streaming/session.cpp index c6f9af9b..c6f2af3c 100644 --- a/app/streaming/session.cpp +++ b/app/streaming/session.cpp @@ -1048,7 +1048,7 @@ bool Session::startConnectionAsync() return false; } - QByteArray hostnameStr = m_Computer->activeAddress.toLatin1(); + QByteArray hostnameStr = m_Computer->activeAddress.address().toLatin1(); QByteArray siAppVersion = m_Computer->appVersion.toLatin1(); SERVER_INFORMATION hostInfo;