From 0532fa15d336a93ac01d07943bed14d124462168 Mon Sep 17 00:00:00 2001 From: Tony Mountifield Date: Fri, 5 Jun 2026 22:53:59 +0100 Subject: [PATCH 01/14] Allow directory to be IPv6 --- src/serverlist.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/serverlist.cpp b/src/serverlist.cpp index 0dafa73863..7b3491d1ed 100644 --- a/src/serverlist.cpp +++ b/src/serverlist.cpp @@ -1012,7 +1012,7 @@ void CServerListManager::SetRegistered ( const bool bIsRegister ) // Allow IPv4 only for communicating with Directories // Use SRV DNS discovery for directory connections, fallback to A/AAAA if none. const QString strNetworkAddress = NetworkUtil::GetDirectoryAddress ( DirectoryType, strDirectoryAddress ); - const bool bDirectoryAddressValid = NetworkUtil::ParseNetworkAddress ( strNetworkAddress, DirectoryAddress, false ); + const bool bDirectoryAddressValid = NetworkUtil::ParseNetworkAddress ( strNetworkAddress, DirectoryAddress, pServer->IsIPv6Available() ); // lock the mutex again now that the address has been resolved. locker.relock(); From dfeca4718be455da3d2509f228ddc10f528a0818 Mon Sep 17 00:00:00 2001 From: Tony Mountifield Date: Sat, 6 Jun 2026 12:41:26 +0100 Subject: [PATCH 02/14] Choose correct public IP for IPv4 or IPv6 --- src/serverlist.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/serverlist.cpp b/src/serverlist.cpp index 7b3491d1ed..186b924fe3 100644 --- a/src/serverlist.cpp +++ b/src/serverlist.cpp @@ -1027,7 +1027,18 @@ void CServerListManager::SetRegistered ( const bool bIsRegister ) // For a registered server, the server properties are stored in the // very first item in the server list (which is actually no server list // but just one item long for the registered server). - pConnLessProtocol->CreateCLRegisterServerExMes ( DirectoryAddress, ServerList[0].LHostAddr, ServerList[0] ); + if ( DirectoryAddress.InetAddr.protocol() == QAbstractSocket::IPv4Protocol ) + { + pConnLessProtocol->CreateCLRegisterServerExMes ( DirectoryAddress, ServerPublicIP, ServerList[0] ); + } + else if ( DirectoryAddress.InetAddr.protocol() == QAbstractSocket::IPv6Protocol ) + { + pConnLessProtocol->CreateCLRegisterServerExMes ( DirectoryAddress, ServerPublicIP6, ServerList[0] ); + } + else + { + SetSvrRegStatus ( SRS_BAD_ADDRESS ); + } } else { From 2bd0d26a1e4f908ea27f2deffb7e55956657efd0 Mon Sep 17 00:00:00 2001 From: Tony Mountifield Date: Sat, 6 Jun 2026 12:50:11 +0100 Subject: [PATCH 03/14] Increase MAX_LEN_IP_ADDRESS for IPv6 --- src/global.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/global.h b/src/global.h index 5414a58c41..bfc0448c1e 100644 --- a/src/global.h +++ b/src/global.h @@ -285,7 +285,7 @@ LED bar: lbr #define MAX_LEN_CHAT_TEXT 1600 #define MAX_LEN_CHAT_TEXT_PLUS_HTML 1800 #define MAX_LEN_SERVER_NAME 20 -#define MAX_LEN_IP_ADDRESS 15 +#define MAX_LEN_IP_ADDRESS 39 // 15 for IPv4, 39 for IPv6 #define MAX_LEN_SERVER_CITY 20 #define MAX_LEN_VERSION_TEXT 50 From 0b794c184a2454df1b88c2d80fafd258306e91c7 Mon Sep 17 00:00:00 2001 From: Tony Mountifield Date: Mon, 8 Jun 2026 23:27:42 +0100 Subject: [PATCH 04/14] Correct declaration parameter name --- src/serverlist.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/serverlist.h b/src/serverlist.h index f18c40678d..171ffb47d3 100644 --- a/src/serverlist.h +++ b/src/serverlist.h @@ -139,7 +139,7 @@ class CServerListEntry : public CServerInfo QString strCountry, QString strNumClients, bool isPermanent, - bool bEnableIPv6 ); + bool bIPv6Available ); QString toCSV(); // time on which the entry was registered From 6848938a07eb4ea8394cf5a285be5e6bb2111b92 Mon Sep 17 00:00:00 2001 From: Tony Mountifield Date: Wed, 10 Jun 2026 16:10:11 +0100 Subject: [PATCH 05/14] Check for IPv6 in clientrpc and connectdlg --- src/clientrpc.cpp | 4 ++-- src/connectdlg.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/clientrpc.cpp b/src/clientrpc.cpp index 0f376d10b4..70370d8681 100644 --- a/src/clientrpc.cpp +++ b/src/clientrpc.cpp @@ -189,8 +189,8 @@ CClientRpc::CClientRpc ( CClient* pClient, CClientSettings* pSettings, CRpcServe CHostAddress haDirectoryAddress; - // Allow IPv4 only for communicating with Directories - if ( !NetworkUtil::ParseNetworkAddress ( jsonDirectoryIp.toString(), haDirectoryAddress, false ) ) + // Allow IPv4 and IPv6 for communicating with Directories + if ( !NetworkUtil::ParseNetworkAddress ( jsonDirectoryIp.toString(), haDirectoryAddress, pClient->IsIPv6Available() ) ) { response["error"] = CRpcServer::CreateJsonRpcError ( CRpcServer::iErrInvalidParams, "Invalid params: directory is not a valid socket address" ); diff --git a/src/connectdlg.cpp b/src/connectdlg.cpp index 5c3913100a..f473e02631 100644 --- a/src/connectdlg.cpp +++ b/src/connectdlg.cpp @@ -319,7 +319,7 @@ void CConnectDlg::RequestServerList() if ( NetworkUtil::ParseNetworkAddress ( NetworkUtil::GetDirectoryAddress ( pSettings->eDirectoryType, pSettings->vstrDirectoryAddress[pSettings->iCustomDirectoryIndex] ), haDirectoryAddress, - false ) ) + pClient->IsIPv6Available() ) ) { // send the request for the server list emit ReqServerListQuery ( haDirectoryAddress ); From b4f9f7ec80844cb0159bba4244ad4bbc3e8c0236 Mon Sep 17 00:00:00 2001 From: Tony Mountifield Date: Sat, 1 Aug 2026 22:39:29 +0100 Subject: [PATCH 06/14] Rename IPv4 addrs and add IPv6 addrs in CServerInfo --- src/clientrpc.cpp | 4 ++-- src/connectdlg.cpp | 4 ++-- src/protocol.cpp | 8 ++++---- src/serverlist.cpp | 44 ++++++++++++++++++++++---------------------- src/testbench.h | 4 ++-- src/util.h | 42 +++++++++++++++++++++++++++++++++--------- 6 files changed, 65 insertions(+), 41 deletions(-) diff --git a/src/clientrpc.cpp b/src/clientrpc.cpp index 70370d8681..1d0439e953 100644 --- a/src/clientrpc.cpp +++ b/src/clientrpc.cpp @@ -137,14 +137,14 @@ CClientRpc::CClientRpc ( CClient* pClient, CClientSettings* pSettings, CRpcServe for ( const auto& serverInfo : vecServerInfo ) { QJsonObject objServerInfo{ - { "address", serverInfo.HostAddr.toString() }, + { "address", serverInfo.HostAddr4.toString() }, { "name", serverInfo.strName }, { "countryId", serverInfo.eCountry }, { "country", QLocale::countryToString ( serverInfo.eCountry ) }, { "city", serverInfo.strCity }, }; arrServerInfo.append ( objServerInfo ); - pClient->CreateCLServerListPingMes ( serverInfo.HostAddr ); + pClient->CreateCLServerListPingMes ( serverInfo.HostAddr4 ); } pRpcServer->BroadcastNotification ( "jamulusclient/serverListReceived", QJsonObject{ diff --git a/src/connectdlg.cpp b/src/connectdlg.cpp index f473e02631..a28511b77f 100644 --- a/src/connectdlg.cpp +++ b/src/connectdlg.cpp @@ -419,7 +419,7 @@ void CConnectDlg::SetServerList ( const CHostAddress& InetAddr, const CVector 0 ) { - CurHostAddress = vecServerInfo[iIdx].HostAddr; + CurHostAddress = vecServerInfo[iIdx].HostAddr4; } else { @@ -447,7 +447,7 @@ void CConnectDlg::SetServerList ( const CHostAddress& InetAddr, const CVectorsetText ( LVC_NAME, CurHostAddress.toString ( CHostAddress::SM_IP_NO_LAST_BYTE ) ); diff --git a/src/protocol.cpp b/src/protocol.cpp index 3030c2dca1..1203ec9e69 100644 --- a/src/protocol.cpp +++ b/src/protocol.cpp @@ -2109,11 +2109,11 @@ void CProtocol::CreateCLServerListMes ( const CHostAddress& InetAddr, const CVec // IP address (4 bytes) // note the Server List manager has put the internal details in HostAddr where required - PutValOnStream ( vecData, iPos, static_cast ( vecServerInfo[i].HostAddr.InetAddr.toIPv4Address() ), 4 ); + PutValOnStream ( vecData, iPos, static_cast ( vecServerInfo[i].HostAddr4.InetAddr.toIPv4Address() ), 4 ); // port number (2 bytes) // note the Server List manager has put the internal details in HostAddr where required - PutValOnStream ( vecData, iPos, static_cast ( vecServerInfo[i].HostAddr.iPort ), 2 ); + PutValOnStream ( vecData, iPos, static_cast ( vecServerInfo[i].HostAddr4.iPort ), 2 ); // country (2 bytes) PutCountryOnStream ( vecData, iPos, vecServerInfo[i].eCountry ); @@ -2232,11 +2232,11 @@ void CProtocol::CreateCLRedServerListMes ( const CHostAddress& InetAddr, const C // IP address (4 bytes) // note the Server List manager has put the internal details in HostAddr where required - PutValOnStream ( vecData, iPos, static_cast ( vecServerInfo[i].HostAddr.InetAddr.toIPv4Address() ), 4 ); + PutValOnStream ( vecData, iPos, static_cast ( vecServerInfo[i].HostAddr4.InetAddr.toIPv4Address() ), 4 ); // port number (2 bytes) // note the Server List manager has put the internal details in HostAddr where required - PutValOnStream ( vecData, iPos, static_cast ( vecServerInfo[i].HostAddr.iPort ), 2 ); + PutValOnStream ( vecData, iPos, static_cast ( vecServerInfo[i].HostAddr4.iPort ), 2 ); // name (note that the string length indicator is 1 in this special case) PutStringUTF8OnStream ( vecData, iPos, strUTF8Name, 1 ); diff --git a/src/serverlist.cpp b/src/serverlist.cpp index 186b924fe3..474cabbf37 100644 --- a/src/serverlist.cpp +++ b/src/serverlist.cpp @@ -130,8 +130,8 @@ QString CServerListEntry::toCSV() { QStringList sl; - sl.append ( this->HostAddr.toString() ); - sl.append ( this->LHostAddr.toString() ); + sl.append ( this->HostAddr4.toString() ); + sl.append ( this->LHostAddr4.toString() ); sl.append ( ToBase64 ( this->strName ) ); sl.append ( ToBase64 ( this->strCity ) ); sl.append ( QString::number ( this->eCountry ) ); @@ -543,7 +543,7 @@ void CServerListManager::OnTimerPingServerInList() for ( int iIdx = 1; iIdx < iCurServerListSize; iIdx++ ) { // send empty message to keep NAT port open at registered server - pConnLessProtocol->CreateCLEmptyMes ( ServerList[iIdx].HostAddr ); + pConnLessProtocol->CreateCLEmptyMes ( ServerList[iIdx].HostAddr4 ); } } @@ -560,7 +560,7 @@ void CServerListManager::OnTimerPollList() if ( ServerList[iIdx].RegisterTime.elapsed() > ( SERVLIST_TIME_OUT_MINUTES * 60000 ) ) { // remove this list entry - vecRemovedHostAddr.Add ( ServerList[iIdx].HostAddr ); + vecRemovedHostAddr.Add ( ServerList[iIdx].HostAddr4 ); ServerList.removeAt ( iIdx ); } } @@ -637,7 +637,7 @@ void CServerListManager::Append ( const CHostAddress& InetAddr, else { // update all data and call update registration function - ServerList[iSelIdx].LHostAddr = LInetAddr; + ServerList[iSelIdx].LHostAddr4 = LInetAddr; ServerList[iSelIdx].strName = ServerInfo.strName; ServerList[iSelIdx].eCountry = ServerInfo.eCountry; ServerList[iSelIdx].strCity = ServerInfo.strCity; @@ -695,12 +695,12 @@ void CServerListManager::RetrieveAll ( const CHostAddress& InetAddr ) bool clientIsInternal = NetworkUtil::IsPrivateNetworkIP ( InetAddr.InetAddr ); CHostAddress clientPublicAddr = InetAddr; - if ( clientIsInternal && CHostAddress().InetAddr != ServerList[0].LHostAddr.InetAddr && - !NetworkUtil::IsPrivateNetworkIP ( ServerList[0].LHostAddr.InetAddr ) ) + if ( clientIsInternal && CHostAddress().InetAddr != ServerList[0].LHostAddr4.InetAddr && + !NetworkUtil::IsPrivateNetworkIP ( ServerList[0].LHostAddr4.InetAddr ) ) { // client and directory on same LAN, directory has public IP set, that should be suitable for the // client, too (i.e. same router with same public IP will be used for both), so use it for client public IP - clientPublicAddr.InetAddr = ServerList[0].LHostAddr.InetAddr; + clientPublicAddr.InetAddr = ServerList[0].LHostAddr4.InetAddr; } const ushort iCurServerListSize = static_cast ( ServerList.size() ); @@ -709,8 +709,8 @@ void CServerListManager::RetrieveAll ( const CHostAddress& InetAddr ) CVector vecServerInfo ( iCurServerListSize ); // copy list item for the directory and just let the protocol sort out the actual details - vecServerInfo[0] = ServerList[0]; - vecServerInfo[0].HostAddr = CHostAddress(); + vecServerInfo[0] = ServerList[0]; + vecServerInfo[0].HostAddr4 = CHostAddress(); // copy the list (we have to copy it since the message requires a vector but the list is actually stored in a QList object // and not in a vector object) @@ -719,25 +719,25 @@ void CServerListManager::RetrieveAll ( const CHostAddress& InetAddr ) // copy list item CServerInfo& siCurListEntry = vecServerInfo[iIdx] = ServerList[iIdx]; - bool serverIsInternal = NetworkUtil::IsPrivateNetworkIP ( siCurListEntry.HostAddr.InetAddr ); + bool serverIsInternal = NetworkUtil::IsPrivateNetworkIP ( siCurListEntry.HostAddr4.InetAddr ); - bool wantHostAddr = clientIsInternal /* HostAddr is local IP if local server else external IP, so do not replace */ || + bool wantHostAddr = clientIsInternal /* HostAddr4 is local IP if local server else external IP, so do not replace */ || ( !serverIsInternal && - InetAddr.InetAddr != siCurListEntry.HostAddr.InetAddr /* external server and client have different public IPs */ ); + InetAddr.InetAddr != siCurListEntry.HostAddr4.InetAddr /* external server and client have different public IPs */ ); if ( !wantHostAddr ) { - vecServerInfo[iIdx].HostAddr = siCurListEntry.LHostAddr; + vecServerInfo[iIdx].HostAddr4 = siCurListEntry.LHostAddr4; } // do not send a "ping" to a server local to the directory (no need) if ( !serverIsInternal ) { // create "send empty message" for all other registered servers - // this causes the server (vecServerInfo[iIdx].HostAddr) + // this causes the server (vecServerInfo[iIdx].HostAddr4) // to send a "reply" to the client (InetAddr or best guess public IP address if internal to directory) // - with the intent of opening the server firewall for the client - pConnLessProtocol->CreateCLSendEmptyMesMes ( siCurListEntry.HostAddr, clientPublicAddr ); + pConnLessProtocol->CreateCLSendEmptyMesMes ( siCurListEntry.HostAddr4, clientPublicAddr ); } } @@ -758,7 +758,7 @@ int CServerListManager::IndexOf ( const CHostAddress& haSearchTerm ) // (i.e., this server). for ( int iIdx = ServerList.size() - 1; iIdx > 0; iIdx-- ) { - if ( ServerList[iIdx].HostAddr == haSearchTerm ) + if ( ServerList[iIdx].HostAddr4 == haSearchTerm ) { return iIdx; } @@ -847,15 +847,15 @@ bool CServerListManager::Load() pServer->IsIPv6Available() ); // We expect servers to have addresses... - if ( ( CHostAddress() == serverListEntry.HostAddr ) ) + if ( ( CHostAddress() == serverListEntry.HostAddr4 ) ) { qWarning() << qUtf8Printable ( QString ( "Could not parse '%1' successfully - invalid host" ).arg ( line ) ); continue; } qInfo() << qUtf8Printable ( QString ( "Loading registration for %1 (%2): %3" ) - .arg ( serverListEntry.HostAddr.toString() ) - .arg ( serverListEntry.LHostAddr.toString() ) + .arg ( serverListEntry.HostAddr4.toString() ) + .arg ( serverListEntry.LHostAddr4.toString() ) .arg ( serverListEntry.strName ) ); ServerList.append ( serverListEntry ); } @@ -889,8 +889,8 @@ void CServerListManager::Save() for ( int iIdx = ServerList.size() - 1; iIdx > 0; iIdx-- ) { qInfo() << qUtf8Printable ( QString ( tr ( "Saving registration for %1 (%2): %3" ) ) - .arg ( ServerList[iIdx].HostAddr.toString() ) - .arg ( ServerList[iIdx].LHostAddr.toString() ) + .arg ( ServerList[iIdx].HostAddr4.toString() ) + .arg ( ServerList[iIdx].LHostAddr4.toString() ) .arg ( ServerList[iIdx].strName ) ); out << ServerList[iIdx].toCSV() << '\n'; } diff --git a/src/testbench.h b/src/testbench.h index 233acd6351..97f5db8c0a 100644 --- a/src/testbench.h +++ b/src/testbench.h @@ -234,8 +234,8 @@ public slots: case 19: // PROTMESSID_CLM_SERVER_LIST vecServerInfo[0].bPermanentOnline = static_cast ( GenRandomIntInRange ( 0, 1 ) ); vecServerInfo[0].eCountry = static_cast ( GenRandomIntInRange ( 0, 100 ) ); - vecServerInfo[0].HostAddr = CurHostAddress; - vecServerInfo[0].LHostAddr = CurLocalAddress; + vecServerInfo[0].HostAddr4 = CurHostAddress; + vecServerInfo[0].LHostAddr4 = CurLocalAddress; vecServerInfo[0].iMaxNumClients = GenRandomIntInRange ( -2, 10000 ); vecServerInfo[0].strCity = GenRandomString(); vecServerInfo[0].strName = GenRandomString(); diff --git a/src/util.h b/src/util.h index 08e45166cb..f9daf62cab 100644 --- a/src/util.h +++ b/src/util.h @@ -1038,7 +1038,7 @@ class CServerCoreInfo class CServerInfo : public CServerCoreInfo { public: - CServerInfo() : HostAddr ( CHostAddress() ), LHostAddr ( CHostAddress() ) {} + CServerInfo() : HostAddr4 ( CHostAddress() ), LHostAddr4 ( CHostAddress() ), HostAddr6 ( CHostAddress() ), LHostAddr6 ( CHostAddress() ) {} CServerInfo ( const CHostAddress& NHAddr, const CHostAddress& NLAddr, @@ -1047,16 +1047,40 @@ class CServerInfo : public CServerCoreInfo const QString& NsCity, const int NiMaxNumClients, const bool NbPermOnline ) : - CServerCoreInfo ( NsName, NeCountry, NsCity, NiMaxNumClients, NbPermOnline ), - HostAddr ( NHAddr ), - LHostAddr ( NLAddr ) - {} + CServerCoreInfo ( NsName, NeCountry, NsCity, NiMaxNumClients, NbPermOnline ) + { + if ( NHAddr.InetAddr.protocol() == QAbstractSocket::IPv4Protocol ) + { + HostAddr4 = NHAddr; + } + + if ( NLAddr.InetAddr.protocol() == QAbstractSocket::IPv4Protocol ) + { + LHostAddr4 = NLAddr; + } + + if ( NHAddr.InetAddr.protocol() == QAbstractSocket::IPv6Protocol ) + { + HostAddr6 = NHAddr; + } + + if ( NLAddr.InetAddr.protocol() == QAbstractSocket::IPv6Protocol ) + { + LHostAddr6 = NLAddr; + } + } + + // IPv4 address of the server + CHostAddress HostAddr4; + + // IPv4 internal address of the server + CHostAddress LHostAddr4; - // internet address of the server - CHostAddress HostAddr; + // IPv6 address of the server + CHostAddress HostAddr6; - // server internal address - CHostAddress LHostAddr; + // IPv6 internal address of the server + CHostAddress LHostAddr6; }; // Network transport properties ------------------------------------------------ From 20f4d8daa7d8166ea9908eda55888f50a123eaab Mon Sep 17 00:00:00 2001 From: Tony Mountifield Date: Wed, 2 Sep 2026 20:21:03 +0100 Subject: [PATCH 07/14] Make invalid CHostAddress properly invalid --- src/util.cpp | 2 +- src/util.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/util.cpp b/src/util.cpp index 39efa10a16..658386ec5e 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -922,7 +922,7 @@ bool NetworkUtil::ParseNetworkAddressSrv ( QString strAddress, CHostAddress& Hos // 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 ); + HostAddress = CHostAddress ( QHostAddress(), 0 ); return true; } qDebug() << qUtf8Printable ( diff --git a/src/util.h b/src/util.h index f9daf62cab..7d4783ff26 100644 --- a/src/util.h +++ b/src/util.h @@ -804,7 +804,7 @@ class CHostAddress SM_IP_NO_LAST_BYTE_PORT }; - CHostAddress() : InetAddr ( static_cast ( 0 ) ), iPort ( 0 ) {} + CHostAddress() : InetAddr(), iPort ( 0 ) {} CHostAddress ( const QHostAddress& NInetAddr, const quint16 iNPort ) : InetAddr ( NInetAddr ), iPort ( iNPort ) {} From d4efb298c23a5e05fedb577e29c8ca6f40a52a59 Mon Sep 17 00:00:00 2001 From: Tony Mountifield Date: Wed, 2 Sep 2026 21:26:41 +0100 Subject: [PATCH 08/14] Rework CHostAddress::toString() --- src/util.cpp | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/src/util.cpp b/src/util.cpp index 658386ec5e..891c9cfdfc 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -1187,35 +1187,39 @@ QString CHostAddress::toString ( const EStringMode eStringMode ) const { QString strReturn = InetAddr.toString(); - // special case: for local host address, we do not replace the last byte - if ( ( ( eStringMode == SM_IP_NO_LAST_BYTE ) || ( eStringMode == SM_IP_NO_LAST_BYTE_PORT ) ) && - ( InetAddr != QHostAddress ( QHostAddress::LocalHost ) ) && ( InetAddr != QHostAddress ( QHostAddress::LocalHostIPv6 ) ) ) + switch ( InetAddr.protocol() ) { - // replace last part by an "x" - if ( strReturn.contains ( "." ) ) + case QAbstractSocket::IPv4Protocol: + if ( ( ( eStringMode == SM_IP_NO_LAST_BYTE ) || ( eStringMode == SM_IP_NO_LAST_BYTE_PORT ) ) && + InetAddr != QHostAddress ( QHostAddress::LocalHost ) ) { - // IPv4 or IPv4-mapped: + // replace last part by an "x" strReturn = strReturn.section ( ".", 0, -2 ) + ".x"; } - else + if ( ( eStringMode == SM_IP_PORT ) || ( eStringMode == SM_IP_NO_LAST_BYTE_PORT ) ) { - // IPv6 - strReturn = strReturn.section ( ":", 0, -2 ) + ":x"; + // add port + strReturn = QString ( "%1:%2" ).arg ( strReturn ).arg ( iPort ); } - } + break; - if ( ( eStringMode == SM_IP_PORT ) || ( eStringMode == SM_IP_NO_LAST_BYTE_PORT ) ) - { - // add port number after a colon - if ( strReturn.contains ( "." ) ) + case QAbstractSocket::IPv6Protocol: + if ( ( ( eStringMode == SM_IP_NO_LAST_BYTE ) || ( eStringMode == SM_IP_NO_LAST_BYTE_PORT ) ) && + InetAddr != QHostAddress ( QHostAddress::LocalHostIPv6 ) ) { - strReturn += ":" + QString().setNum ( iPort ); + // replace last part by an "x" + strReturn = strReturn.section ( ":", 0, -2 ) + ":x"; } - else + if ( ( eStringMode == SM_IP_PORT ) || ( eStringMode == SM_IP_NO_LAST_BYTE_PORT ) ) { // enclose pure IPv6 address in [ ] before adding port, to avoid ambiguity - strReturn = "[" + strReturn + "]:" + QString().setNum ( iPort ); + strReturn = QString ( "[%1]:%2" ).arg ( strReturn ).arg ( iPort ); } + break; + + default: + // do not append port to an invalid address + break; } return strReturn; From b5e92aaed185639d82c5e6c95523ce56ed501128 Mon Sep 17 00:00:00 2001 From: Tony Mountifield Date: Wed, 26 Aug 2026 18:06:08 +0100 Subject: [PATCH 09/14] Add JSON-RPC to fetch server list from the directory This it to aid testing of the IPv6 directory support, but will be a useful addition in its own right. --- src/server.h | 2 ++ src/serverlist.cpp | 25 ++++++++++++++ src/serverlist.h | 2 ++ src/serverrpc.cpp | 86 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 115 insertions(+) diff --git a/src/server.h b/src/server.h index c54c3083cd..846eb1f47f 100644 --- a/src/server.h +++ b/src/server.h @@ -166,6 +166,8 @@ class CServer : public QObject, public CServerSlots void SetServerCountry ( const QLocale::Country eNewCountry ) { ServerListManager.SetServerCountry ( eNewCountry ); } QLocale::Country GetServerCountry() { return ServerListManager.GetServerCountry(); } + bool GetDirectoryServerList ( CVector& vecServerInfo ) { return ServerListManager.GetDirectoryServerList ( vecServerInfo ); } + bool GetRecorderInitialised() { return JamController.GetRecorderInitialised(); } void SetEnableRecording ( bool bNewEnableRecording ); bool GetDisableRecording() { return bDisableRecording; } diff --git a/src/serverlist.cpp b/src/serverlist.cpp index 474cabbf37..79cdcb2628 100644 --- a/src/serverlist.cpp +++ b/src/serverlist.cpp @@ -749,6 +749,31 @@ void CServerListManager::RetrieveAll ( const CHostAddress& InetAddr ) } } +// fetch copy of registered server list for use by JSON-RPC +// return false if not a directory +bool CServerListManager::GetDirectoryServerList ( CVector& vecServerInfo ) +{ + QMutexLocker locker ( &Mutex ); + + if ( !bIsDirectory ) + { + return false; + } + + const ushort iCurServerListSize = static_cast ( ServerList.size() ); + + // allocate memory for the entire list + vecServerInfo.Init ( iCurServerListSize ); + + // copy the list + for ( int iIdx = 0; iIdx < iCurServerListSize; iIdx++ ) + { + vecServerInfo[iIdx] = ServerList[iIdx]; + } + + return true; +} + int CServerListManager::IndexOf ( const CHostAddress& haSearchTerm ) { // Called with lock set. diff --git a/src/serverlist.h b/src/serverlist.h index 171ffb47d3..b9aa607b5a 100644 --- a/src/serverlist.h +++ b/src/serverlist.h @@ -195,6 +195,8 @@ class CServerListManager : public QObject void Remove ( const CHostAddress& InetAddr ); void RetrieveAll ( const CHostAddress& InetAddr ); + bool GetDirectoryServerList ( CVector& vecServerInfo ); + void StoreRegistrationResult ( ESvrRegResult eStatus ); QString GetServerListFileName() { return ServerListFileName; } diff --git a/src/serverrpc.cpp b/src/serverrpc.cpp index a35aaad17f..70183968ee 100644 --- a/src/serverrpc.cpp +++ b/src/serverrpc.cpp @@ -384,6 +384,92 @@ CServerRpc::CServerRpc ( CServer* pServer, CRpcServer* pRpcServer, QObject* pare response["result"] = "acknowledged"; Q_UNUSED ( params ); } ); + + /// @rpc_method jamulusdirectory/getServerList + /// @brief Returns the list of registered servers along with details about them. + /// @param {object} params - No parameters (empty object). + /// @result {number} result.numservers - The number of registered servers. + /// @result {array} result.servers - The list of registered servers. + /// @result {number} result.servers[*].id - The server's index. + /// @result {string} result.servers[*].name - The server's name. + /// @result {string} result.servers[*].countryName - The text name of the country specified by the user for this channel (see + /// QLocale::Country). + /// @result {string} result.servers[*].city - The city name provided by the operator of this server. + /// @result {number} result.servers[*].maxClients - The max number of clients supported by the server. + /// @result {bool} result.servers[*].permanent - The permanent status of the server. + /// @result {string} result.servers[*].ipv4addr - The IPv4 address the server registered from (ip:port) + /// @result {string} result.servers[*].ipv4addrLocal - The local IPv4 address provided by the server (ip:port) + /// @result {string} result.servers[*].ipv6addr - The IPv6 address the server registered from ([ipv6]:port) + /// @result {string} result.servers[*].ipv6addrLocal - The local IPv6 address provided by the server ([ipv6]:port) + pRpcServer->HandleMethod ( "jamulusdirectory/getServerList", [=] ( const QJsonObject& params, QJsonObject& response ) { + QJsonArray servers; + CVector vecServerInfo; + + int isDirectory = pServer->GetDirectoryServerList ( vecServerInfo ); + + if ( !isDirectory ) + { + response["error"] = CRpcServer::CreateJsonRpcError ( CRpcServer::iErrInvalidParams, "Server is not a directory" ); + return; + } + + const int iNumServers = vecServerInfo.Size(); + + // fill list with connected clients + for ( int i = 0; i < iNumServers; i++ ) + { + + // name of the server + // QString strName; + + // country in which the server is located + // QLocale::Country eCountry; + + // city in which the server is located + // QString strCity; + + // maximum number of clients which can connect to the server at the same + // time + // int iMaxNumClients; + + // is the server permanently online or not (flag) + // bool bPermanentOnline; + + // IPv4 address of the server + // CHostAddress HostAddr4; + + // IPv4 internal address of the server + // CHostAddress LHostAddr4; + + // IPv6 address of the server + // CHostAddress HostAddr6; + + // IPv6 internal address of the server + // CHostAddress LHostAddr6; + + QJsonObject server{ + { "id", i }, + { "name", vecServerInfo[i].strName }, + { "countryName", QLocale::countryToString ( vecServerInfo[i].eCountry ) }, + { "city", vecServerInfo[i].strCity }, + { "maxClients", vecServerInfo[i].iMaxNumClients }, + { "permanent", vecServerInfo[i].bPermanentOnline }, + { "ipv4addr", vecServerInfo[i].HostAddr4.toString() }, + { "ipv4addrLocal", vecServerInfo[i].LHostAddr4.toString() }, + { "ipv6addr", vecServerInfo[i].HostAddr6.toString() }, + { "ipv6addrLocal", vecServerInfo[i].LHostAddr6.toString() }, + }; + servers.append ( server ); + } + + // create result object + QJsonObject result{ + { "numservers", iNumServers }, + { "servers", servers }, + }; + response["result"] = result; + Q_UNUSED ( params ); + } ); } #if defined( Q_OS_MACOS ) && QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 ) From 5172a2aa0c2f0c350ef74ec397093cc43900d503 Mon Sep 17 00:00:00 2001 From: Tony Mountifield Date: Wed, 2 Sep 2026 22:39:43 +0100 Subject: [PATCH 10/14] Update JSON-RPC.md --- docs/JSON-RPC.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/docs/JSON-RPC.md b/docs/JSON-RPC.md index 3501fbdc9d..2c181cc83f 100644 --- a/docs/JSON-RPC.md +++ b/docs/JSON-RPC.md @@ -376,6 +376,34 @@ Results: | result | string | Always "ok". | +### jamulusdirectory/getServerList + +Returns the list of registered servers along with details about them. + +Parameters: + +| Name | Type | Description | +| --- | --- | --- | +| params | object | No parameters (empty object). | + +Results: + +| Name | Type | Description | +| --- | --- | --- | +| result.numservers | number | The number of registered servers. | +| result.servers | array | The list of registered servers. | +| result.servers[*].id | number | The server's index. | +| result.servers[*].name | string | The server's name. | +| result.servers[*].countryName | string | The text name of the country specified by the user for this channel (see QLocale::Country). | +| result.servers[*].city | string | The city name provided by the operator of this server. | +| result.servers[*].maxClients | number | The max number of clients supported by the server. | +| result.servers[*].permanent | bool | The permanent status of the server. | +| result.servers[*].ipv4addr | string | The IPv4 address the server registered from (ip:port) | +| result.servers[*].ipv4addrLocal | string | The local IPv4 address provided by the server (ip:port) | +| result.servers[*].ipv6addr | string | The IPv6 address the server registered from ([ipv6]:port) | +| result.servers[*].ipv6addrLocal | string | The local IPv6 address provided by the server ([ipv6]:port) | + + ### jamulusserver/broadcastChatMessage Sends a message (as the server) to all connected clients. This can be used to broadcast messages from external sources (e.g. scripts or monitoring tools). From 238d0c1d1891c4ad4148535e523ca2b68ad92874 Mon Sep 17 00:00:00 2001 From: Tony Mountifield Date: Thu, 3 Sep 2026 16:29:50 +0100 Subject: [PATCH 11/14] Remove unneeded constructor for CServerListEntry --- src/serverlist.cpp | 4 +++- src/serverlist.h | 12 ------------ 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/src/serverlist.cpp b/src/serverlist.cpp index 79cdcb2628..d06b10f6e4 100644 --- a/src/serverlist.cpp +++ b/src/serverlist.cpp @@ -212,7 +212,9 @@ CServerListManager::CServerListManager ( CServer* pServer, * * If we are a directory, we assume that we are a permanent server. */ - CServerListEntry ThisServerListEntry ( haServerAddr, ServerPublicIP, "", QLocale::system().country(), "", iNumChannels, bIsDirectory ); + CServerListEntry ThisServerListEntry ( haServerAddr, + ServerPublicIP, + CServerCoreInfo ( "", QLocale::system().country(), "", iNumChannels, bIsDirectory ) ); // parse the server info string according to definition: // [this server name];[this server city];[this server country as QLocale ID] (; ... ignored) diff --git a/src/serverlist.h b/src/serverlist.h index b9aa607b5a..0f9ba611fb 100644 --- a/src/serverlist.h +++ b/src/serverlist.h @@ -106,18 +106,6 @@ class CServerListEntry : public CServerInfo public: CServerListEntry() : CServerInfo ( CHostAddress(), CHostAddress(), "", QLocale::AnyCountry, "", 0, false ) { UpdateRegistration(); } - CServerListEntry ( const CHostAddress& NHAddr, - const CHostAddress& NLHAddr, - const QString& NsName, - const QLocale::Country& NeCountry, - const QString& NsCity, - const int NiMaxNumClients, - const bool NbPermOnline ) : - CServerInfo ( NHAddr, NLHAddr, NsName, NeCountry, NsCity, NiMaxNumClients, NbPermOnline ) - { - UpdateRegistration(); - } - CServerListEntry ( const CHostAddress& NHAddr, const CHostAddress& NLHAddr, const CServerCoreInfo& NewCoreServerInfo ) : CServerInfo ( NHAddr, NLHAddr, From 657a94f81d28a1f372d6c341a34e33d0ff990759 Mon Sep 17 00:00:00 2001 From: Tony Mountifield Date: Thu, 3 Sep 2026 16:56:15 +0100 Subject: [PATCH 12/14] Move CServerListEntry constructors from .h to .cpp --- src/serverlist.cpp | 14 ++++++++++++++ src/serverlist.h | 15 ++------------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/serverlist.cpp b/src/serverlist.cpp index d06b10f6e4..7faae7a4b3 100644 --- a/src/serverlist.cpp +++ b/src/serverlist.cpp @@ -77,6 +77,20 @@ from clients not on the same LAN as the directory. /* Implementation *************************************************************/ // --- CServerListEntry --- +CServerListEntry::CServerListEntry() : CServerInfo ( CHostAddress(), CHostAddress(), "", QLocale::AnyCountry, "", 0, false ) { UpdateRegistration(); } + +CServerListEntry::CServerListEntry ( const CHostAddress& NHAddr, const CHostAddress& NLHAddr, const CServerCoreInfo& NewCoreServerInfo ) : + CServerInfo ( NHAddr, + NLHAddr, + NewCoreServerInfo.strName, + NewCoreServerInfo.eCountry, + NewCoreServerInfo.strCity, + NewCoreServerInfo.iMaxNumClients, + NewCoreServerInfo.bPermanentOnline ) +{ + UpdateRegistration(); +} + CServerListEntry CServerListEntry::parse ( QString strHAddr, QString strLHAddr, QString sName, diff --git a/src/serverlist.h b/src/serverlist.h index 0f9ba611fb..fb286fa4a1 100644 --- a/src/serverlist.h +++ b/src/serverlist.h @@ -104,19 +104,8 @@ class CServer; class CServerListEntry : public CServerInfo { public: - CServerListEntry() : CServerInfo ( CHostAddress(), CHostAddress(), "", QLocale::AnyCountry, "", 0, false ) { UpdateRegistration(); } - - CServerListEntry ( const CHostAddress& NHAddr, const CHostAddress& NLHAddr, const CServerCoreInfo& NewCoreServerInfo ) : - CServerInfo ( NHAddr, - NLHAddr, - NewCoreServerInfo.strName, - NewCoreServerInfo.eCountry, - NewCoreServerInfo.strCity, - NewCoreServerInfo.iMaxNumClients, - NewCoreServerInfo.bPermanentOnline ) - { - UpdateRegistration(); - } + CServerListEntry(); + CServerListEntry ( const CHostAddress& NHAddr, const CHostAddress& NLHAddr, const CServerCoreInfo& NewCoreServerInfo ); void UpdateRegistration() { RegisterTime.start(); } From 70df0de75ca6bfefd7657adc81c8aad788d21a2d Mon Sep 17 00:00:00 2001 From: Tony Mountifield Date: Thu, 3 Sep 2026 23:54:46 +0100 Subject: [PATCH 13/14] Search for match using correct IP protocol --- src/serverlist.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/serverlist.cpp b/src/serverlist.cpp index 7faae7a4b3..eb54e7f4dc 100644 --- a/src/serverlist.cpp +++ b/src/serverlist.cpp @@ -799,7 +799,11 @@ int CServerListManager::IndexOf ( const CHostAddress& haSearchTerm ) // (i.e., this server). for ( int iIdx = ServerList.size() - 1; iIdx > 0; iIdx-- ) { - if ( ServerList[iIdx].HostAddr4 == haSearchTerm ) + if ( haSearchTerm.InetAddr.protocol() == QAbstractSocket::IPv4Protocol && ServerList[iIdx].HostAddr4 == haSearchTerm ) + { + return iIdx; + } + if ( haSearchTerm.InetAddr.protocol() == QAbstractSocket::IPv6Protocol && ServerList[iIdx].HostAddr6 == haSearchTerm ) { return iIdx; } From d0c74d10d90d0e8e8ff6c680191719c4731dbb7e Mon Sep 17 00:00:00 2001 From: Tony Mountifield Date: Thu, 3 Sep 2026 17:56:42 +0100 Subject: [PATCH 14/14] Add registration token --- src/serverlist.cpp | 3 ++- src/serverlist.h | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/serverlist.cpp b/src/serverlist.cpp index eb54e7f4dc..f660eedb87 100644 --- a/src/serverlist.cpp +++ b/src/serverlist.cpp @@ -86,7 +86,8 @@ CServerListEntry::CServerListEntry ( const CHostAddress& NHAddr, const CHostAddr NewCoreServerInfo.eCountry, NewCoreServerInfo.strCity, NewCoreServerInfo.iMaxNumClients, - NewCoreServerInfo.bPermanentOnline ) + NewCoreServerInfo.bPermanentOnline ), + token ( QRandomGenerator::system()->generate() ) { UpdateRegistration(); } diff --git a/src/serverlist.h b/src/serverlist.h index fb286fa4a1..819680aba7 100644 --- a/src/serverlist.h +++ b/src/serverlist.h @@ -89,6 +89,7 @@ Note: this mechanism will not work in a private network. #include #include #include +#include #include #if QT_VERSION >= QT_VERSION_CHECK( 5, 6, 0 ) # include @@ -128,6 +129,8 @@ class CServerListEntry : public CServerInfo static QString ToBase64 ( const QString strIn ) { return ToBase64 ( strIn.toUtf8() ); } static QByteArray FromBase64ToByteArray ( const QString strIn ) { return QByteArray::fromBase64 ( strIn.toLatin1() ); } static QString FromBase64ToString ( const QString strIn ) { return QString::fromUtf8 ( FromBase64ToByteArray ( strIn ) ); } + + quint32 token; }; class CServerListManager : public QObject