From 24adc20b5d1740e5be21bbc7492f6218b1071b18 Mon Sep 17 00:00:00 2001 From: Christian Hoffmann Date: Wed, 19 Oct 2022 10:25:07 +0200 Subject: [PATCH 1/2] Internal: Refactor NetworkUtil::ParseNetworkAddress This moves the host elaborate and reusable host name lookup logic to its own ParseNetworkAddressName method. Co-authored-by: Peter L Jones --- src/util.cpp | 51 ++++++++++++++++++++++++++------------------------- src/util.h | 1 + 2 files changed, 27 insertions(+), 25 deletions(-) diff --git a/src/util.cpp b/src/util.cpp index b4ec0a34de..e226e95bbd 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -889,6 +889,31 @@ QSize CMinimumStackedLayout::sizeHint() const * Other Classes * \******************************************************************************/ // Network utility functions --------------------------------------------------- +bool NetworkUtil::ParseNetworkAddressString ( QString strAddress, QHostAddress& InetAddr, bool bEnableIPv6 ) +{ + // try to get host by name, assuming + // that the string contains a valid host name string or IP address + const QHostInfo HostInfo = QHostInfo::fromName ( strAddress ); + + if ( HostInfo.error() != QHostInfo::NoError ) + { + // qInfo() << qUtf8Printable ( QString ( "Invalid hostname" ) ); + return false; // invalid address + } + + foreach ( const QHostAddress HostAddr, HostInfo.addresses() ) + { + // qInfo() << qUtf8Printable ( QString ( "Resolved network address to %1 for proto %2" ) .arg ( HostAddr.toString() ) .arg ( + // HostAddr.protocol() ) ); + if ( HostAddr.protocol() == QAbstractSocket::IPv4Protocol || ( bEnableIPv6 && HostAddr.protocol() == QAbstractSocket::IPv6Protocol ) ) + { + InetAddr = HostAddr; + return true; + } + } + return false; +} + bool NetworkUtil::ParseNetworkAddress ( QString strAddress, CHostAddress& HostAddress, bool bEnableIPv6 ) { QHostAddress InetAddr; @@ -970,31 +995,7 @@ bool NetworkUtil::ParseNetworkAddress ( QString strAddress, CHostAddress& HostAd return false; // invalid address } - // try to get host by name, assuming - // that the string contains a valid host name string - const QHostInfo HostInfo = QHostInfo::fromName ( strAddress ); - - if ( HostInfo.error() != QHostInfo::NoError ) - { - // qInfo() << qUtf8Printable ( QString ( "Invalid hostname" ) ); - return false; // invalid address - } - - bool bFoundAddr = false; - - foreach ( const QHostAddress HostAddr, HostInfo.addresses() ) - { - // qInfo() << qUtf8Printable ( QString ( "Resolved network address to %1 for proto %2" ) .arg ( HostAddr.toString() ) .arg ( - // HostAddr.protocol() ) ); - if ( HostAddr.protocol() == QAbstractSocket::IPv4Protocol || ( bEnableIPv6 && HostAddr.protocol() == QAbstractSocket::IPv6Protocol ) ) - { - InetAddr = HostAddr; - bFoundAddr = true; - break; - } - } - - if ( !bFoundAddr ) + if ( !ParseNetworkAddressString ( strAddress, InetAddr, bEnableIPv6 ) ) { // no valid address found // qInfo() << qUtf8Printable ( QString ( "No IP address found for hostname" ) ); diff --git a/src/util.h b/src/util.h index 4a711e9b0b..32e55e95c0 100644 --- a/src/util.h +++ b/src/util.h @@ -1038,6 +1038,7 @@ class CNetworkTransportProps class NetworkUtil { public: + static bool ParseNetworkAddressString ( QString strAddress, QHostAddress& InetAddr, bool bEnableIPv6 ); static bool ParseNetworkAddress ( QString strAddress, CHostAddress& HostAddress, bool bEnableIPv6 ); static QString FixAddress ( const QString& strAddress ); From ed18ad6615beb8d97eb099ee67615167b9fbf03f Mon Sep 17 00:00:00 2001 From: Christian Hoffmann Date: Wed, 19 Oct 2022 10:29:22 +0200 Subject: [PATCH 2/2] 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 | 4 +++ src/util.cpp | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/util.h | 13 ++++++++-- 3 files changed, 84 insertions(+), 2 deletions(-) diff --git a/src/client.cpp b/src/client.cpp index ad9beb624c..2c2e9999ac 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -477,7 +477,11 @@ void CClient::StartDelayTimer() bool CClient::SetServerAddr ( QString strNAddr ) { CHostAddress HostAddress; +#ifdef CLIENT_NO_SRV_CONNECT if ( NetworkUtil().ParseNetworkAddress ( strNAddr, HostAddress, bEnableIPv6 ) ) +#else + if ( NetworkUtil().ParseNetworkAddressWithSrvDiscovery ( strNAddr, HostAddress, bEnableIPv6 ) ) +#endif { // apply address to the channel Channel.SetAddress ( HostAddress ); diff --git a/src/util.cpp b/src/util.cpp index e226e95bbd..0235b77b71 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -914,6 +914,75 @@ bool NetworkUtil::ParseNetworkAddressString ( QString strAddress, QHostAddress& return false; } +#ifndef CLIENT_NO_SRV_CONNECT +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, the "." part might be redundant, but this would + // need further testing to confirm. + // 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 ( ParseNetworkAddressString ( 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 via plain IP or host name lookup (A/AAAA): + return ParseNetworkAddress ( strAddress, HostAddress, bEnableIPv6 ); +} +#endif + bool NetworkUtil::ParseNetworkAddress ( QString strAddress, CHostAddress& HostAddress, bool bEnableIPv6 ) { QHostAddress InetAddr; diff --git a/src/util.h b/src/util.h index 32e55e95c0..b5681d723e 100644 --- a/src/util.h +++ b/src/util.h @@ -53,6 +53,9 @@ #include #include #include +#ifndef CLIENT_NO_SRV_CONNECT +# include +#endif #ifndef _WIN32 # include #endif @@ -79,8 +82,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 +1043,11 @@ class NetworkUtil { public: static bool ParseNetworkAddressString ( QString strAddress, QHostAddress& InetAddr, bool bEnableIPv6 ); + +#ifndef CLIENT_NO_SRV_CONNECT + static bool ParseNetworkAddressSrv ( QString strAddress, CHostAddress& HostAddress, bool bEnableIPv6 ); + static bool ParseNetworkAddressWithSrvDiscovery ( QString strAddress, CHostAddress& HostAddress, bool bEnableIPv6 ); +#endif static bool ParseNetworkAddress ( QString strAddress, CHostAddress& HostAddress, bool bEnableIPv6 ); static QString FixAddress ( const QString& strAddress );