From b5ceddded18ada809d29ee6cbac64b7f636a599a Mon Sep 17 00:00:00 2001 From: Christian Hoffmann Date: Wed, 19 Oct 2022 10:29:22 +0200 Subject: [PATCH] Client: Add SRV-based virtual hosting support When connecting to host names without port (e.g. example.org), the client will now perform an SRV lookup to _jamulus._udp.example.org. If this lookup returns exactly one result, the result is used to select the actual target address/port. If the lookup does not return a usable result, the regular connect logic kicks in (A lookup, default port). Related: https://github.com/orgs/jamulussoftware/discussions/1772 --- src/client.cpp | 2 +- src/util.cpp | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/util.h | 8 ++++-- 3 files changed, 73 insertions(+), 3 deletions(-) diff --git a/src/client.cpp b/src/client.cpp index ad9beb624c..d1c0a28d49 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -477,7 +477,7 @@ void CClient::StartDelayTimer() bool CClient::SetServerAddr ( QString strNAddr ) { CHostAddress HostAddress; - if ( NetworkUtil().ParseNetworkAddress ( strNAddr, HostAddress, bEnableIPv6 ) ) + if ( NetworkUtil().ParseNetworkAddressWithSrvDiscovery ( strNAddr, HostAddress, bEnableIPv6 ) ) { // apply address to the channel Channel.SetAddress ( HostAddress ); diff --git a/src/util.cpp b/src/util.cpp index 38708f1966..b40c5e76e9 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -914,6 +914,72 @@ bool NetworkUtil::ParseNetworkAddressName ( QString strAddress, QHostAddress& In return false; } +bool NetworkUtil::ParseNetworkAddressSrv ( QString strAddress, CHostAddress& HostAddress, bool bEnableIPv6 ) +{ + // init requested host address with invalid address first + HostAddress = CHostAddress(); + + QRegularExpression plainHostRegex ( "^([^\\[:0-9.][^:]*)$" ); + if ( plainHostRegex.match ( strAddress ).capturedStart() != 0 ) + { + // not a plain hostname? then don't attempt SRV lookup and fail + // immediately. + return false; + } + + QDnsLookup* dns = new QDnsLookup(); + dns->setType ( QDnsLookup::SRV ); + dns->setName ( QString ( "_jamulus._udp.%1" ).arg ( strAddress ) ); + dns->lookup(); + // QDnsLookup::lookup() works asynchronously. Therefore, wait for + // it to complete here by resuming the main loop here. + // This is not nice and blocks the UI, but is similar to what + // the regular resolve function does as well. + QTime dieTime = QTime::currentTime().addMSecs ( DNS_SRV_RESOLVE_TIMEOUT_MS ); + while ( QTime::currentTime() < dieTime && !dns->isFinished() ) + { + QCoreApplication::processEvents ( QEventLoop::ExcludeUserInputEvents, 100 ); + } + QList records = dns->serviceRecords(); + dns->deleteLater(); + if ( records.length() != 1 ) + { + return false; + } + QDnsServiceRecord record = records.first(); + if ( record.target() == "." || record.target() == "" ) + { + // RFC2782 says that "." indicates that the service is not available. + // (Qt strips the trailing dot, which is why we check for empty string + // as well) + // Therefore, end processing here (= return true), but pass back an + // invalid HostAddress to let the connect logic fail properly. + HostAddress = CHostAddress ( QHostAddress ( "." ), 0 ); + return true; + } + qDebug() << qUtf8Printable ( + QString ( "resolved %1 to a single SRV record: %2:%3" ).arg ( strAddress ).arg ( record.target() ).arg ( record.port() ) ); + + QHostAddress InetAddr; + if ( ParseNetworkAddressName ( record.target(), InetAddr, bEnableIPv6 ) ) + { + HostAddress = CHostAddress ( InetAddr, record.port() ); + return true; + } + return false; +} + +bool NetworkUtil::ParseNetworkAddressWithSrvDiscovery ( QString strAddress, CHostAddress& HostAddress, bool bEnableIPv6 ) +{ + // Try SRV-based discovery first: + if ( ParseNetworkAddressSrv ( strAddress, HostAddress, bEnableIPv6 ) ) + { + return true; + } + // Try regular connect: + return ParseNetworkAddress ( strAddress, HostAddress, bEnableIPv6 ); +} + bool NetworkUtil::ParseNetworkAddress ( QString strAddress, CHostAddress& HostAddress, bool bEnableIPv6 ) { QHostAddress InetAddr; diff --git a/src/util.h b/src/util.h index 47d562475c..d440e27d2d 100644 --- a/src/util.h +++ b/src/util.h @@ -42,6 +42,7 @@ #include #include #include +#include #include #include #include @@ -79,8 +80,9 @@ class CClient; // forward declaration of CClient #endif /* Definitions ****************************************************************/ -#define METER_FLY_BACK 2 -#define INVALID_MIDI_CH -1 // invalid MIDI channel definition +#define METER_FLY_BACK 2 +#define INVALID_MIDI_CH -1 // invalid MIDI channel definition +#define DNS_SRV_RESOLVE_TIMEOUT_MS 500 /* Global functions ***********************************************************/ // converting float to short @@ -1039,6 +1041,8 @@ class NetworkUtil { public: static bool ParseNetworkAddressName ( QString strAddress, QHostAddress& InetAddr, bool bEnableIPv6 ); + static bool ParseNetworkAddressSrv ( QString strAddress, CHostAddress& HostAddress, bool bEnableIPv6 ); + static bool ParseNetworkAddressWithSrvDiscovery ( QString strAddress, CHostAddress& HostAddress, bool bEnableIPv6 ); static bool ParseNetworkAddress ( QString strAddress, CHostAddress& HostAddress, bool bEnableIPv6 ); static QString FixAddress ( const QString& strAddress );