From 9968e41868b2e7ca6fd9fc5a171b777855160d1a Mon Sep 17 00:00:00 2001 From: Jiri Popek Date: Thu, 18 Feb 2021 19:33:03 +0100 Subject: [PATCH 1/2] IPv6 support Note that it is backwards compatible with IPv4. Co-authored-by: Tony Mountifield --- src/socket.cpp | 165 +++++++++++++++++++++++++++++++++++++------------ 1 file changed, 127 insertions(+), 38 deletions(-) diff --git a/src/socket.cpp b/src/socket.cpp index 94d254920f..880b52d62a 100644 --- a/src/socket.cpp +++ b/src/socket.cpp @@ -25,9 +25,19 @@ #include "socket.h" #include "server.h" +#ifdef _WIN32 +#include +#include +#else +#include +#endif + + /* Implementation *************************************************************/ void CSocket::Init ( const quint16 iPortNumber, const quint16 iQosNumber, const QString& strServerBindIP ) { + bool bUseIPV6 = true; + #ifdef _WIN32 // for the Windows socket usage we have to start it up first @@ -40,25 +50,46 @@ void CSocket::Init ( const quint16 iPortNumber, const quint16 iQosNumber, const #endif // create the UDP socket - UdpSocket = socket ( AF_INET, SOCK_DGRAM, 0 ); - // - const char tos = (char) iQosNumber; // Quality of Service - setsockopt ( UdpSocket, IPPROTO_IP, IP_TOS, &tos, sizeof ( tos ) ); + UdpSocket = socket ( AF_INET6, SOCK_DGRAM, 0 ); + if ( UdpSocket == -1 ) + { + // IPv6 not available; fall back to IPv4 + bUseIPV6 = false; + + UdpSocket = socket ( AF_INET, SOCK_DGRAM, 0 ); + // + const char tos = (char) iQosNumber; // Quality of Service + setsockopt ( UdpSocket, IPPROTO_IP, IP_TOS, &tos, sizeof ( tos ) ); + } + else + { + // The IPV6_V6ONLY socket option must be false in order for the socket to listen on both protocols. + // On Linux it's false by default on most (all?) distros, but on Windows it is true by default + const uint8_t no = 0; + setsockopt( UdpSocket, IPPROTO_IPV6, IPV6_V6ONLY, (const char *)&no, sizeof( no ) ); + // + const char tos = (char) iQosNumber; // Quality of Service + setsockopt ( UdpSocket, IPPROTO_IPV6, IPV6_TCLASS, &tos, sizeof ( tos ) ); + } // allocate memory for network receive and send buffer in samples vecbyRecBuf.Init ( MAX_SIZE_BYTES_NETW_BUF ); // preinitialize socket in address (only the port number is missing) sockaddr_in UdpSocketInAddr; - UdpSocketInAddr.sin_family = AF_INET; - if ( strServerBindIP.isEmpty() ) - { - UdpSocketInAddr.sin_addr.s_addr = INADDR_ANY; - } - else + UdpSocketInAddr.sin_family = AF_INET; + UdpSocketInAddr.sin_addr.s_addr = INADDR_ANY; + + struct sockaddr_in6 UdpSocketIn6Addr; + UdpSocketIn6Addr.sin6_family = AF_INET6; + UdpSocketIn6Addr.sin6_addr = in6addr_any; + + // TODO - ALLOw IPV6 ADDRESS + if ( !strServerBindIP.isEmpty() ) { UdpSocketInAddr.sin_addr.s_addr = htonl ( QHostAddress ( strServerBindIP ).toIPv4Address() ); } + // END TODO - ALLOw IPV6 ADDRESS // initialize the listening socket bool bSuccess; @@ -68,9 +99,18 @@ void CSocket::Init ( const quint16 iPortNumber, const quint16 iQosNumber, const if ( iPortNumber == 0 ) { // if port number is 0, bind the client to a random available port - UdpSocketInAddr.sin_port = htons ( 0 ); + if (bUseIPV6) { + UdpSocketIn6Addr.sin6_port = htons ( 0 ); + bSuccess = ( ::bind ( UdpSocket , + (sockaddr*) &UdpSocketIn6Addr, + sizeof ( struct sockaddr_in6 ) ) == 0 ); + } else { + UdpSocketInAddr.sin_port = htons ( 0 ); + bSuccess = ( ::bind ( UdpSocket , + (sockaddr*) &UdpSocketInAddr, + sizeof ( sockaddr_in ) ) == 0 ); + } - bSuccess = ( ::bind ( UdpSocket, (sockaddr*) &UdpSocketInAddr, sizeof ( sockaddr_in ) ) == 0 ); } else { @@ -86,9 +126,17 @@ void CSocket::Init ( const quint16 iPortNumber, const quint16 iQosNumber, const while ( !bSuccess && ( iClientPortIncrement <= NUM_SOCKET_PORTS_TO_TRY ) ) { - UdpSocketInAddr.sin_port = htons ( startingPortNumber + iClientPortIncrement ); - - bSuccess = ( ::bind ( UdpSocket, (sockaddr*) &UdpSocketInAddr, sizeof ( sockaddr_in ) ) == 0 ); + if (bUseIPV6) { + UdpSocketIn6Addr.sin6_port = htons ( startingPortNumber + iClientPortIncrement ); + bSuccess = ( ::bind ( UdpSocket , + (sockaddr*) &UdpSocketIn6Addr, + sizeof ( struct sockaddr_in6 ) ) == 0 ); + } else { + UdpSocketInAddr.sin_port = htons ( startingPortNumber + iClientPortIncrement ); + bSuccess = ( ::bind ( UdpSocket , + (sockaddr*) &UdpSocketInAddr, + sizeof ( sockaddr_in ) ) == 0 ); + } iClientPortIncrement++; } @@ -99,9 +147,17 @@ void CSocket::Init ( const quint16 iPortNumber, const quint16 iQosNumber, const // for the server, only try the given port number and do not try out // other port numbers to bind since it is important that the server // gets the desired port number - UdpSocketInAddr.sin_port = htons ( iPortNumber ); - - bSuccess = ( ::bind ( UdpSocket, (sockaddr*) &UdpSocketInAddr, sizeof ( sockaddr_in ) ) == 0 ); + if (bUseIPV6) { + UdpSocketIn6Addr.sin6_port = htons ( iPortNumber ); + bSuccess = ( ::bind ( UdpSocket , + (sockaddr*) &UdpSocketIn6Addr, + sizeof ( struct sockaddr_in6 ) ) == 0 ); + } else { + UdpSocketInAddr.sin_port = htons ( iPortNumber ); + bSuccess = ( ::bind ( UdpSocket , + (sockaddr*) &UdpSocketInAddr, + sizeof ( sockaddr_in ) ) == 0 ); + } } if ( !bSuccess ) @@ -181,19 +237,36 @@ void CSocket::SendPacket ( const CVector& vecbySendBuf, const CHostAddr // send packet through network (we have to convert the constant unsigned // char vector in "const char*", for this we first convert the const // uint8_t vector in a read/write uint8_t vector and then do the cast to - // const char*) - sockaddr_in UdpSocketOutAddr; - - UdpSocketOutAddr.sin_family = AF_INET; - UdpSocketOutAddr.sin_port = htons ( HostAddr.iPort ); - UdpSocketOutAddr.sin_addr.s_addr = htonl ( HostAddr.InetAddr.toIPv4Address() ); - - sendto ( UdpSocket, - (const char*) &( (CVector) vecbySendBuf )[0], - iVecSizeOut, - 0, - (sockaddr*) &UdpSocketOutAddr, - sizeof ( sockaddr_in ) ); + // const char *) + if (HostAddr.InetAddr.protocol() == QAbstractSocket::IPv4Protocol) { + + sockaddr_in UdpSocketOutAddr; + UdpSocketOutAddr.sin_family = AF_INET; + UdpSocketOutAddr.sin_port = htons ( HostAddr.iPort ); + UdpSocketOutAddr.sin_addr.s_addr = htonl ( HostAddr.InetAddr.toIPv4Address() ); + + sendto ( UdpSocket, + (const char*) &( (CVector) vecbySendBuf )[0], + iVecSizeOut, + 0, + (sockaddr*) &UdpSocketOutAddr, + sizeof ( UdpSocketOutAddr ) ); + + } else { + + sockaddr_in6 UdpSocketOutAddr; + + UdpSocketOutAddr.sin6_family = AF_INET6; + UdpSocketOutAddr.sin6_port = htons ( HostAddr.iPort ); + inet_pton(AF_INET6, HostAddr.InetAddr.toString().toLocal8Bit().constData(), &UdpSocketOutAddr.sin6_addr); + + sendto ( UdpSocket, + (const char*) &( (CVector) vecbySendBuf )[0], + iVecSizeOut, + 0, + (sockaddr*) &UdpSocketOutAddr, + sizeof ( UdpSocketOutAddr ) ); + } } } @@ -223,14 +296,19 @@ void CSocket::OnDataReceived() */ // read block from network interface and query address of sender - sockaddr_in SenderAddr; + struct sockaddr_storage addrstorage; #ifdef _WIN32 - int SenderAddrSize = sizeof ( sockaddr_in ); + int SenderAddrSize = sizeof ( struct sockaddr_storage ); #else - socklen_t SenderAddrSize = sizeof ( sockaddr_in ); + socklen_t SenderAddrSize = sizeof ( struct sockaddr_storage ); #endif - const long iNumBytesRead = recvfrom ( UdpSocket, (char*) &vecbyRecBuf[0], MAX_SIZE_BYTES_NETW_BUF, 0, (sockaddr*) &SenderAddr, &SenderAddrSize ); + const long iNumBytesRead = recvfrom ( UdpSocket, + (char*) &vecbyRecBuf[0], + MAX_SIZE_BYTES_NETW_BUF, + 0, + (sockaddr*) &addrstorage, + &SenderAddrSize ); // check if an error occurred or no data could be read if ( iNumBytesRead <= 0 ) @@ -238,9 +316,20 @@ void CSocket::OnDataReceived() return; } - // convert address of client - RecHostAddr.InetAddr.setAddress ( ntohl ( SenderAddr.sin_addr.s_addr ) ); - RecHostAddr.iPort = ntohs ( SenderAddr.sin_port ); + if ( addrstorage.ss_family == AF_INET6) { + struct sockaddr_in6 *in6 = reinterpret_cast(&addrstorage); + if (IN6_IS_ADDR_V4MAPPED(&(in6->sin6_addr))) { + RecHostAddr.InetAddr.setAddress ( ntohl (*((uint32_t*)in6->sin6_addr.s6_addr + 3) ) ); + } else { + RecHostAddr.InetAddr.setAddress( in6->sin6_addr.s6_addr ); + } + RecHostAddr.iPort = ntohs ( in6->sin6_port ); + } else { + struct sockaddr_in *in = reinterpret_cast(&addrstorage); + // convert address of client + RecHostAddr.InetAddr.setAddress ( ntohl ( in->sin_addr.s_addr ) ); + RecHostAddr.iPort = ntohs ( in->sin_port ); + } // check if this is a protocol message int iRecCounter; From 576d10125c9800da16b151679bcaf24ea3f1f4ac Mon Sep 17 00:00:00 2001 From: Tony Mountifield Date: Sat, 17 Jul 2021 20:35:37 +0100 Subject: [PATCH 2/2] Further work on IPv6 support Reworked ParseNetworkAddress() to use regex to validate format. Allows literal IP address (IP4 or IP6) between [ ], or else IP4 address or hostname without [ ]. Optional :port follows. Add code to determine routable IPv6 interface address. Uses Cloudflare's IPv6 DNs resolver address (no traffic sent). Use LocalHostIPv6 if no local IPv6 address Add command option and flag for enabling IPv6 Reworked sockaddr handling in OnDataReceived() Change in_port_t to uint16_t for Windows compatibility Allow IPv4 only for communicating with Directory Servers Rework dual-stack socket handling for Windows compatibility Move sockaddr union definition into socket.h --- src/client.cpp | 10 +- src/client.h | 4 +- src/clientdlg.cpp | 6 +- src/clientdlg.h | 2 + src/connectdlg.cpp | 16 ++- src/connectdlg.h | 3 +- src/global.h | 3 +- src/main.cpp | 23 ++++- src/server.cpp | 5 +- src/server.h | 7 ++ src/serverdlg.cpp | 4 +- src/serverlist.cpp | 28 +++-- src/serverlist.h | 6 +- src/socket.cpp | 250 +++++++++++++++++++++++++-------------------- src/socket.h | 32 ++++-- src/util.cpp | 118 ++++++++++++++++----- src/util.h | 3 +- 17 files changed, 343 insertions(+), 177 deletions(-) diff --git a/src/client.cpp b/src/client.cpp index 367972abdb..240a2e122b 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -31,6 +31,7 @@ CClient::CClient ( const quint16 iPortNumber, const QString& strMIDISetup, const bool bNoAutoJackConnect, const QString& strNClientName, + const bool bNEnableIPv6, const bool bNMuteMeInPersonalMix ) : ChannelInfo(), strClientName ( strNClientName ), @@ -46,7 +47,7 @@ CClient::CClient ( const quint16 iPortNumber, bIsInitializationPhase ( true ), bMuteOutStream ( false ), fMuteOutStreamGain ( 1.0f ), - Socket ( &Channel, iPortNumber, iQosNumber ), + Socket ( &Channel, iPortNumber, iQosNumber, "", bNEnableIPv6 ), Sound ( AudioCallback, this, strMIDISetup, bNoAutoJackConnect, strNClientName ), iAudioInFader ( AUD_FADER_IN_MIDDLE ), bReverbOnLeftChan ( false ), @@ -62,7 +63,8 @@ CClient::CClient ( const quint16 iPortNumber, eGUIDesign ( GD_ORIGINAL ), bEnableOPUS64 ( false ), bJitterBufferOK ( true ), - bNuteMeInPersonalMix ( bNMuteMeInPersonalMix ), + bEnableIPv6 ( bNEnableIPv6 ), + bMuteMeInPersonalMix ( bNMuteMeInPersonalMix ), iServerSockBufNumFrames ( DEF_NET_BUF_SIZE_NUM_BL ), pSignalHandler ( CSignalHandler::getSingletonP() ) { @@ -344,7 +346,7 @@ void CClient::SetRemoteChanGain ( const int iId, const float fGain, const bool b bool CClient::SetServerAddr ( QString strNAddr ) { CHostAddress HostAddress; - if ( NetworkUtil().ParseNetworkAddress ( strNAddr, HostAddress ) ) + if ( NetworkUtil().ParseNetworkAddress ( strNAddr, HostAddress, bEnableIPv6 ) ) { // apply address to the channel Channel.SetAddress ( HostAddress ); @@ -706,7 +708,7 @@ void CClient::OnClientIDReceived ( int iChanID ) // for headless mode we support to mute our own signal in the personal mix // (note that the check for headless is done in the main.cpp and must not // be checked here) - if ( bNuteMeInPersonalMix ) + if ( bMuteMeInPersonalMix ) { SetRemoteChanGain ( iChanID, 0, false ); } diff --git a/src/client.h b/src/client.h index 929da28853..ca82328080 100644 --- a/src/client.h +++ b/src/client.h @@ -113,6 +113,7 @@ class CClient : public QObject const QString& strMIDISetup, const bool bNoAutoJackConnect, const QString& strNClientName, + const bool bNEnableIPv6, const bool bNMuteMeInPersonalMix ); virtual ~CClient(); @@ -347,7 +348,8 @@ class CClient : public QObject bool bEnableOPUS64; bool bJitterBufferOK; - bool bNuteMeInPersonalMix; + bool bEnableIPv6; + bool bMuteMeInPersonalMix; QMutex MutexDriverReinit; // server settings diff --git a/src/clientdlg.cpp b/src/clientdlg.cpp index 01f44ddc0e..46a2ecd6aa 100644 --- a/src/clientdlg.cpp +++ b/src/clientdlg.cpp @@ -32,12 +32,14 @@ CClientDlg::CClientDlg ( CClient* pNCliP, const bool bNewShowComplRegConnList, const bool bShowAnalyzerConsole, const bool bMuteStream, + const bool bNEnableIPv6, QWidget* parent ) : CBaseDlg ( parent, Qt::Window ), // use Qt::Window to get min/max window buttons pClient ( pNCliP ), pSettings ( pNSetP ), bConnectDlgWasShown ( false ), bMIDICtrlUsed ( !strMIDISetup.isEmpty() ), + bEnableIPv6 ( bNEnableIPv6 ), eLastRecorderState ( RS_UNDEFINED ), // for SetMixerBoardDeco eLastDesign ( GD_ORIGINAL ), // " ClientSettingsDlg ( pNCliP, pNSetP, parent ), @@ -560,12 +562,12 @@ CClientDlg::CClientDlg ( CClient* pNCliP, // Send the request to two servers for redundancy if either or both of them // has a higher release version number, the reply will trigger the notification. - if ( NetworkUtil().ParseNetworkAddress ( UPDATECHECK1_ADDRESS, UpdateServerHostAddress ) ) + if ( NetworkUtil().ParseNetworkAddress ( UPDATECHECK1_ADDRESS, UpdateServerHostAddress, bEnableIPv6 ) ) { pClient->CreateCLServerListReqVerAndOSMes ( UpdateServerHostAddress ); } - if ( NetworkUtil().ParseNetworkAddress ( UPDATECHECK2_ADDRESS, UpdateServerHostAddress ) ) + if ( NetworkUtil().ParseNetworkAddress ( UPDATECHECK2_ADDRESS, UpdateServerHostAddress, bEnableIPv6 ) ) { pClient->CreateCLServerListReqVerAndOSMes ( UpdateServerHostAddress ); } diff --git a/src/clientdlg.h b/src/clientdlg.h index 4d2ce7e182..5437fb8b2a 100644 --- a/src/clientdlg.h +++ b/src/clientdlg.h @@ -81,6 +81,7 @@ class CClientDlg : public CBaseDlg, private Ui_CClientDlgBase const bool bNewShowComplRegConnList, const bool bShowAnalyzerConsole, const bool bMuteStream, + const bool bNEnableIPv6, QWidget* parent = nullptr ); protected: @@ -104,6 +105,7 @@ class CClientDlg : public CBaseDlg, private Ui_CClientDlgBase bool bConnectDlgWasShown; bool bMIDICtrlUsed; bool bDetectFeedback; + bool bEnableIPv6; ERecorderState eLastRecorderState; EGUIDesign eLastDesign; QTimer TimerSigMet; diff --git a/src/connectdlg.cpp b/src/connectdlg.cpp index 4b89d6b492..0f2b37db24 100644 --- a/src/connectdlg.cpp +++ b/src/connectdlg.cpp @@ -25,7 +25,7 @@ #include "connectdlg.h" /* Implementation *************************************************************/ -CConnectDlg::CConnectDlg ( CClientSettings* pNSetP, const bool bNewShowCompleteRegList, QWidget* parent ) : +CConnectDlg::CConnectDlg ( CClientSettings* pNSetP, const bool bNewShowCompleteRegList, const bool bNEnableIPv6, QWidget* parent ) : CBaseDlg ( parent, Qt::Dialog ), pSettings ( pNSetP ), strSelectedAddress ( "" ), @@ -35,7 +35,8 @@ CConnectDlg::CConnectDlg ( CClientSettings* pNSetP, const bool bNewShowCompleteR bReducedServerListReceived ( false ), bServerListItemWasChosen ( false ), bListFilterWasActive ( false ), - bShowAllMusicians ( true ) + bShowAllMusicians ( true ), + bEnableIPv6 ( bNEnableIPv6 ) { setupUi ( this ); @@ -228,10 +229,13 @@ void CConnectDlg::RequestServerList() // function) when the connect dialog is opened, this seems to be the correct // time to do it. Note that in case of custom directory server address we // use iCustomDirectoryIndex as an index into the vector. + + // Allow IPv4 only for communicating with Directory Servers if ( NetworkUtil().ParseNetworkAddress ( NetworkUtil::GetCentralServerAddress ( pSettings->eCentralServerAddressType, pSettings->vstrCentralServerAddress[pSettings->iCustomDirectoryIndex] ), - CentralServerAddress ) ) + CentralServerAddress, + false ) ) { // send the request for the server list emit ReqServerListQuery ( CentralServerAddress ); @@ -730,7 +734,9 @@ void CConnectDlg::OnTimerPing() // try to parse host address string which is stored as user data // in the server list item GUI control element - if ( NetworkUtil().ParseNetworkAddress ( lvwServers->topLevelItem ( iIdx )->data ( 0, Qt::UserRole ).toString(), CurServerAddress ) ) + if ( NetworkUtil().ParseNetworkAddress ( lvwServers->topLevelItem ( iIdx )->data ( 0, Qt::UserRole ).toString(), + CurServerAddress, + bEnableIPv6 ) ) { // if address is valid, send ping message using a new thread QtConcurrent::run ( this, &CConnectDlg::EmitCLServerListPingMes, CurServerAddress ); @@ -953,4 +959,4 @@ void CConnectDlg::UpdateDirectoryServerComboBox() cbxDirectoryServer->addItem ( pSettings->vstrCentralServerAddress[i], i ); } } -} \ No newline at end of file +} diff --git a/src/connectdlg.h b/src/connectdlg.h index d704fd1736..ab6785e425 100644 --- a/src/connectdlg.h +++ b/src/connectdlg.h @@ -48,7 +48,7 @@ class CConnectDlg : public CBaseDlg, private Ui_CConnectDlgBase Q_OBJECT public: - CConnectDlg ( CClientSettings* pNSetP, const bool bNewShowCompleteRegList, QWidget* parent = nullptr ); + CConnectDlg ( CClientSettings* pNSetP, const bool bNewShowCompleteRegList, const bool bNEnableIPv6, QWidget* parent = nullptr ); void SetShowAllMusicians ( const bool bState ) { ShowAllMusicians ( bState ); } bool GetShowAllMusicians() { return bShowAllMusicians; } @@ -90,6 +90,7 @@ class CConnectDlg : public CBaseDlg, private Ui_CConnectDlgBase bool bServerListItemWasChosen; bool bListFilterWasActive; bool bShowAllMusicians; + bool bEnableIPv6; public slots: void OnServerListItemDoubleClicked ( QTreeWidgetItem* Item, int ); diff --git a/src/global.h b/src/global.h index 63222b4eee..121c350bbf 100644 --- a/src/global.h +++ b/src/global.h @@ -119,7 +119,8 @@ LED bar: lbr // We just need a valid, public Internet IP here. We will not send any // traffic there as we will only set up an UDP socket without sending any // data. -#define WELL_KNOWN_HOST "1.1.1.1" // CloudFlare +#define WELL_KNOWN_HOST "1.1.1.1" // CloudFlare +#define WELL_KNOWN_HOST6 "2606:4700:4700::1111" // CloudFlare #define WELL_KNOWN_PORT DEFAULT_PORT_NUMBER #define IP_LOOKUP_TIMEOUT 500 // ms diff --git a/src/main.cpp b/src/main.cpp index ab0deb4acd..f6c3836dbf 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -81,6 +81,7 @@ int main ( int argc, char** argv ) bool bNoAutoJackConnect = false; bool bUseTranslation = true; bool bCustomPortNumberGiven = false; + bool bEnableIPv6 = false; int iNumServerChannels = DEFAULT_USED_NUM_CHANNELS; quint16 iPortNumber = DEFAULT_PORT_NUMBER; quint16 iQosNumber = DEFAULT_QOS_NUMBER; @@ -179,6 +180,15 @@ int main ( int argc, char** argv ) continue; } + // Enable IPv6 --------------------------------------------------------- + if ( GetFlagArgument ( argv, i, "-6", "--enableipv6" ) ) + { + bEnableIPv6 = true; + qInfo() << "- IPv6 enabled"; + CommandLineOptions << "--enableipv6"; + continue; + } + // Server only: // Disconnect all clients on quit -------------------------------------- @@ -784,8 +794,14 @@ int main ( int argc, char** argv ) { // Client: // actual client object - CClient - Client ( iPortNumber, iQosNumber, strConnOnStartupAddress, strMIDISetup, bNoAutoJackConnect, strClientName, bMuteMeInPersonalMix ); + CClient Client ( iPortNumber, + iQosNumber, + strConnOnStartupAddress, + strMIDISetup, + bNoAutoJackConnect, + strClientName, + bEnableIPv6, + bMuteMeInPersonalMix ); // load settings from init-file (command line options override) CClientSettings Settings ( &Client, strIniFileName ); @@ -809,6 +825,7 @@ int main ( int argc, char** argv ) bShowComplRegConnList, bShowAnalyzerConsole, bMuteStream, + bEnableIPv6, nullptr ); // show dialog @@ -846,6 +863,7 @@ int main ( int argc, char** argv ) bUseMultithreading, bDisableRecording, bDelayPan, + bEnableIPv6, eLicenceType ); #ifndef HEADLESS @@ -937,6 +955,7 @@ QString UsageArguments ( char** argv ) " -Q, --qos set the QoS value. Default is 128. Disable with 0\n" " (see the Jamulus website to enable QoS on Windows)\n" " -t, --notranslation disable translation (use English language)\n" + " -6, --enableipv6 enable IPv6 addressing (IPv4 is always enabled)\n" "\n" "Server only:\n" " -d, --discononquit disconnect all clients on quit\n" diff --git a/src/server.cpp b/src/server.cpp index 579891f9f0..6635a3e796 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -230,11 +230,12 @@ CServer::CServer ( const int iNewMaxNumChan, const bool bNUseMultithreading, const bool bDisableRecording, const bool bNDelayPan, + const bool bNEnableIPv6, const ELicenceType eNLicenceType ) : bUseDoubleSystemFrameSize ( bNUseDoubleSystemFrameSize ), bUseMultithreading ( bNUseMultithreading ), iMaxNumChannels ( iNewMaxNumChan ), - Socket ( this, iPortNumber, iQosNumber, strServerBindIP ), + Socket ( this, iPortNumber, iQosNumber, strServerBindIP, bNEnableIPv6 ), Logging(), iFrameCount ( 0 ), bWriteStatusHTMLFile ( false ), @@ -247,11 +248,13 @@ CServer::CServer ( const int iNewMaxNumChan, strServerPublicIP, strServerListFilter, iNewMaxNumChan, + bNEnableIPv6, &ConnLessProtocol ), JamController ( this ), bDisableRecording ( bDisableRecording ), bAutoRunMinimized ( false ), bDelayPan ( bNDelayPan ), + bEnableIPv6 ( bNEnableIPv6 ), eLicenceType ( eNLicenceType ), bDisconnectAllClientsOnQuit ( bNDisconnectAllClientsOnQuit ), pSignalHandler ( CSignalHandler::getSingletonP() ) diff --git a/src/server.h b/src/server.h index 35fe74522d..f7ed776ee5 100644 --- a/src/server.h +++ b/src/server.h @@ -170,6 +170,7 @@ class CServer : public QObject, public CServerSlots const bool bNUseMultithreading, const bool bDisableRecording, const bool bNDelayPan, + const bool bNEnableIPv6, const ELicenceType eNLicenceType ); virtual ~CServer(); @@ -209,6 +210,9 @@ class CServer : public QObject, public CServerSlots void SetEnableDelayPanning ( bool bDelayPanningOn ) { bDelayPan = bDelayPanningOn; } bool IsDelayPanningEnabled() { return bDelayPan; } + // IPv6 Enabled + bool IsIPv6Enabled() { return bEnableIPv6; } + // Server list management -------------------------------------------------- void UpdateServerList() { ServerListManager.Update(); } @@ -368,6 +372,9 @@ class CServer : public QObject, public CServerSlots // for delay panning bool bDelayPan; + // enable IPv6 + bool bEnableIPv6; + // messaging QString strWelcomeMessage; ELicenceType eLicenceType; diff --git a/src/serverdlg.cpp b/src/serverdlg.cpp index 3388579ba6..c3bb286ca9 100644 --- a/src/serverdlg.cpp +++ b/src/serverdlg.cpp @@ -433,12 +433,12 @@ lvwClients->setMinimumHeight ( 140 ); // Send the request to two servers for redundancy if either or both of them // has a higher release version number, the reply will trigger the notification. - if ( NetworkUtil().ParseNetworkAddress ( UPDATECHECK1_ADDRESS, UpdateServerHostAddress ) ) + if ( NetworkUtil().ParseNetworkAddress ( UPDATECHECK1_ADDRESS, UpdateServerHostAddress, pServer->IsIPv6Enabled() ) ) { pServer->CreateCLServerListReqVerAndOSMes ( UpdateServerHostAddress ); } - if ( NetworkUtil().ParseNetworkAddress ( UPDATECHECK2_ADDRESS, UpdateServerHostAddress ) ) + if ( NetworkUtil().ParseNetworkAddress ( UPDATECHECK2_ADDRESS, UpdateServerHostAddress, pServer->IsIPv6Enabled() ) ) { pServer->CreateCLServerListReqVerAndOSMes ( UpdateServerHostAddress ); } diff --git a/src/serverlist.cpp b/src/serverlist.cpp index 86cf093b8c..1807905c1d 100644 --- a/src/serverlist.cpp +++ b/src/serverlist.cpp @@ -33,10 +33,11 @@ CServerListEntry CServerListEntry::parse ( QString strHAddr, QString sCity, QString strCountry, QString strNumClients, - bool isPermanent ) + bool isPermanent, + bool bEnableIPv6 ) { CHostAddress haServerHostAddr; - NetworkUtil::ParseNetworkAddress ( strHAddr, haServerHostAddr ); + NetworkUtil::ParseNetworkAddress ( strHAddr, haServerHostAddr, bEnableIPv6 ); if ( CHostAddress() == haServerHostAddr ) { // do not proceed without server host address! @@ -44,7 +45,7 @@ CServerListEntry CServerListEntry::parse ( QString strHAddr, } CHostAddress haServerLocalAddr; - NetworkUtil::ParseNetworkAddress ( strLHAddr, haServerLocalAddr ); + NetworkUtil::ParseNetworkAddress ( strLHAddr, haServerLocalAddr, bEnableIPv6 ); if ( haServerLocalAddr.iPort == 0 ) { haServerLocalAddr.iPort = haServerHostAddr.iPort; @@ -98,8 +99,10 @@ CServerListManager::CServerListManager ( const quint16 iNPortNum, const QString& strServerListFilter, const QString& strServerPublicIP, const int iNumChannels, + const bool bNEnableIPv6, CProtocol* pNConLProt ) : eCentralServerAddressType ( AT_CUSTOM ), // must be AT_CUSTOM for the "no GUI" case + bEnableIPv6 ( bNEnableIPv6 ), eSvrRegStatus ( SRS_UNREGISTERED ), strMinServerVersion ( "" ), // disable version check with empty version pConnLessProtocol ( pNConLProt ), @@ -110,6 +113,7 @@ CServerListManager::CServerListManager ( const quint16 iNPortNum, // set the server internal address, including internal port number QHostAddress qhaServerPublicIP; + if ( strServerPublicIP == "" ) { // No user-supplied override via --serverpublicip -> use auto-detection @@ -123,6 +127,16 @@ CServerListManager::CServerListManager ( const quint16 iNPortNum, qDebug() << "Using" << qhaServerPublicIP.toString() << "as external IP."; SlaveCurLocalHostAddress = CHostAddress ( qhaServerPublicIP, iNPortNum ); + if ( bEnableIPv6 ) + { + // set the server internal address, including internal port number + QHostAddress qhaServerPublicIP6; + + qhaServerPublicIP6 = NetworkUtil::GetLocalAddress6().InetAddr; + qDebug() << "Using" << qhaServerPublicIP6.toString() << "as external IPv6."; + SlaveCurLocalHostAddress6 = CHostAddress ( qhaServerPublicIP6, iNPortNum ); + } + // prepare the server info information QStringList slServInfoSeparateParams; int iServInfoNumSplitItems = 0; @@ -592,7 +606,7 @@ void CServerListManager::CentralServerLoadServerList ( const QString strServerLi continue; } - NetworkUtil::ParseNetworkAddress ( slLine[0], haServerHostAddr ); + NetworkUtil::ParseNetworkAddress ( slLine[0], haServerHostAddr, bEnableIPv6 ); int iIdx = IndexOf ( haServerHostAddr ); if ( iIdx != INVALID_INDEX ) { @@ -601,7 +615,7 @@ void CServerListManager::CentralServerLoadServerList ( const QString strServerLi } CServerListEntry serverListEntry = - CServerListEntry::parse ( slLine[0], slLine[1], slLine[2], slLine[3], slLine[4], slLine[5], slLine[6].toInt() != 0 ); + CServerListEntry::parse ( slLine[0], slLine[1], slLine[2], slLine[3], slLine[4], slLine[5], slLine[6].toInt() != 0, bEnableIPv6 ); // We expect servers to have addresses... if ( ( CHostAddress() == serverListEntry.HostAddr ) ) @@ -737,7 +751,9 @@ void CServerListManager::SlaveServerRegisterServer ( const bool bIsRegister ) // Note that we always have to parse the server address again since if // it is an URL of a dynamic IP address, the IP address might have // changed in the meanwhile. - if ( NetworkUtil().ParseNetworkAddress ( strCurCentrServAddr, SlaveCurCentServerHostAddress ) ) + + // Allow IPv4 only for communicating with Directory Servers + if ( NetworkUtil().ParseNetworkAddress ( strCurCentrServAddr, SlaveCurCentServerHostAddress, false ) ) { if ( bIsRegister ) { diff --git a/src/serverlist.h b/src/serverlist.h index 9dbaecd00b..207498f514 100644 --- a/src/serverlist.h +++ b/src/serverlist.h @@ -113,7 +113,8 @@ class CServerListEntry : public CServerInfo QString sCity, QString strCountry, QString strNumClients, - bool isPermanent ); + bool isPermanent, + bool bEnableIPv6 ); QString toCSV(); // time on which the entry was registered @@ -139,6 +140,7 @@ class CServerListManager : public QObject const QString& strServerListFilter, const QString& strServerPublicIP, const int iNumChannels, + const bool bNEnableIPv6, CProtocol* pNConLProt ); void SetEnabled ( const bool bState ) { bEnabled = bState; } @@ -198,12 +200,14 @@ class CServerListManager : public QObject QString strCentralServerAddress; ECSAddType eCentralServerAddressType; bool bIsCentralServer; + bool bEnableIPv6; // server registration status ESvrRegStatus eSvrRegStatus; CHostAddress SlaveCurCentServerHostAddress; CHostAddress SlaveCurLocalHostAddress; + CHostAddress SlaveCurLocalHostAddress6; QString ServerListFileName; diff --git a/src/socket.cpp b/src/socket.cpp index 880b52d62a..d7cdb9a699 100644 --- a/src/socket.cpp +++ b/src/socket.cpp @@ -26,17 +26,19 @@ #include "server.h" #ifdef _WIN32 -#include -#include +# include +# include #else -#include +# include #endif - /* Implementation *************************************************************/ void CSocket::Init ( const quint16 iPortNumber, const quint16 iQosNumber, const QString& strServerBindIP ) { - bool bUseIPV6 = true; + uSockAddr UdpSocketAddr; + + int UdpSocketAddrLen; + uint16_t* UdpPort; #ifdef _WIN32 // for the Windows socket usage we have to start it up first @@ -49,47 +51,71 @@ void CSocket::Init ( const quint16 iPortNumber, const quint16 iQosNumber, const WSAStartup ( MAKEWORD ( 1, 0 ), &wsa ); #endif - // create the UDP socket - UdpSocket = socket ( AF_INET6, SOCK_DGRAM, 0 ); - if ( UdpSocket == -1 ) - { - // IPv6 not available; fall back to IPv4 - bUseIPV6 = false; + memset ( &UdpSocketAddr, 0, sizeof ( UdpSocketAddr ) ); - UdpSocket = socket ( AF_INET, SOCK_DGRAM, 0 ); - // - const char tos = (char) iQosNumber; // Quality of Service - setsockopt ( UdpSocket, IPPROTO_IP, IP_TOS, &tos, sizeof ( tos ) ); - } - else + if ( bEnableIPv6 ) { + // try to create a IPv6 UDP socket + UdpSocket = socket ( AF_INET6, SOCK_DGRAM, 0 ); + if ( UdpSocket == -1 ) + { + // IPv6 requested but not available, throw error + throw CGenErr ( "IPv6 requested but not available on this system.", "Network Error" ); + } + // The IPV6_V6ONLY socket option must be false in order for the socket to listen on both protocols. // On Linux it's false by default on most (all?) distros, but on Windows it is true by default const uint8_t no = 0; - setsockopt( UdpSocket, IPPROTO_IPV6, IPV6_V6ONLY, (const char *)&no, sizeof( no ) ); - // + setsockopt ( UdpSocket, IPPROTO_IPV6, IPV6_V6ONLY, (const char*) &no, sizeof ( no ) ); + + // set the QoS const char tos = (char) iQosNumber; // Quality of Service setsockopt ( UdpSocket, IPPROTO_IPV6, IPV6_TCLASS, &tos, sizeof ( tos ) ); - } - // allocate memory for network receive and send buffer in samples - vecbyRecBuf.Init ( MAX_SIZE_BYTES_NETW_BUF ); + UdpSocketAddr.sa6.sin6_family = AF_INET6; + UdpSocketAddr.sa6.sin6_addr = in6addr_any; + UdpSocketAddrLen = sizeof ( UdpSocketAddr.sa6 ); - // preinitialize socket in address (only the port number is missing) - sockaddr_in UdpSocketInAddr; - UdpSocketInAddr.sin_family = AF_INET; - UdpSocketInAddr.sin_addr.s_addr = INADDR_ANY; + UdpPort = &UdpSocketAddr.sa6.sin6_port; // where to put the port number - struct sockaddr_in6 UdpSocketIn6Addr; - UdpSocketIn6Addr.sin6_family = AF_INET6; - UdpSocketIn6Addr.sin6_addr = in6addr_any; + // FIXME: If binding a dual-protocol interface to a specific address, does it cease to be dual-protocol? - // TODO - ALLOw IPV6 ADDRESS - if ( !strServerBindIP.isEmpty() ) + // TODO - ALLOW IPV6 ADDRESS + // if ( !strServerBindIP.isEmpty() ) + //{ + // UdpSocketInAddr.sin_addr.s_addr = htonl ( QHostAddress ( strServerBindIP ).toIPv4Address() ); + //} + // END TODO - ALLOW IPV6 ADDRESS + } + else { - UdpSocketInAddr.sin_addr.s_addr = htonl ( QHostAddress ( strServerBindIP ).toIPv4Address() ); + // create the UDP socket for IPv4 + UdpSocket = socket ( AF_INET, SOCK_DGRAM, 0 ); + if ( UdpSocket == -1 ) + { + // IPv4 requested but not available, throw error (should never happen, but check anyway) + throw CGenErr ( "IPv4 requested but not available on this system.", "Network Error" ); + } + + // set the QoS + const char tos = (char) iQosNumber; // Quality of Service + setsockopt ( UdpSocket, IPPROTO_IP, IP_TOS, &tos, sizeof ( tos ) ); + + // preinitialize socket in address (only the port number is missing) + UdpSocketAddr.sa4.sin_family = AF_INET; + UdpSocketAddr.sa4.sin_addr.s_addr = INADDR_ANY; + UdpSocketAddrLen = sizeof ( UdpSocketAddr.sa4 ); + + UdpPort = &UdpSocketAddr.sa4.sin_port; // where to put the port number + + if ( !strServerBindIP.isEmpty() ) + { + UdpSocketAddr.sa4.sin_addr.s_addr = htonl ( QHostAddress ( strServerBindIP ).toIPv4Address() ); + } } - // END TODO - ALLOw IPV6 ADDRESS + + // allocate memory for network receive and send buffer in samples + vecbyRecBuf.Init ( MAX_SIZE_BYTES_NETW_BUF ); // initialize the listening socket bool bSuccess; @@ -99,18 +125,9 @@ void CSocket::Init ( const quint16 iPortNumber, const quint16 iQosNumber, const if ( iPortNumber == 0 ) { // if port number is 0, bind the client to a random available port - if (bUseIPV6) { - UdpSocketIn6Addr.sin6_port = htons ( 0 ); - bSuccess = ( ::bind ( UdpSocket , - (sockaddr*) &UdpSocketIn6Addr, - sizeof ( struct sockaddr_in6 ) ) == 0 ); - } else { - UdpSocketInAddr.sin_port = htons ( 0 ); - bSuccess = ( ::bind ( UdpSocket , - (sockaddr*) &UdpSocketInAddr, - sizeof ( sockaddr_in ) ) == 0 ); - } + *UdpPort = htons ( 0 ); + bSuccess = ( ::bind ( UdpSocket, &UdpSocketAddr.sa, UdpSocketAddrLen ) == 0 ); } else { @@ -126,17 +143,9 @@ void CSocket::Init ( const quint16 iPortNumber, const quint16 iQosNumber, const while ( !bSuccess && ( iClientPortIncrement <= NUM_SOCKET_PORTS_TO_TRY ) ) { - if (bUseIPV6) { - UdpSocketIn6Addr.sin6_port = htons ( startingPortNumber + iClientPortIncrement ); - bSuccess = ( ::bind ( UdpSocket , - (sockaddr*) &UdpSocketIn6Addr, - sizeof ( struct sockaddr_in6 ) ) == 0 ); - } else { - UdpSocketInAddr.sin_port = htons ( startingPortNumber + iClientPortIncrement ); - bSuccess = ( ::bind ( UdpSocket , - (sockaddr*) &UdpSocketInAddr, - sizeof ( sockaddr_in ) ) == 0 ); - } + *UdpPort = htons ( startingPortNumber + iClientPortIncrement ); + + bSuccess = ( ::bind ( UdpSocket, &UdpSocketAddr.sa, UdpSocketAddrLen ) == 0 ); iClientPortIncrement++; } @@ -147,25 +156,15 @@ void CSocket::Init ( const quint16 iPortNumber, const quint16 iQosNumber, const // for the server, only try the given port number and do not try out // other port numbers to bind since it is important that the server // gets the desired port number - if (bUseIPV6) { - UdpSocketIn6Addr.sin6_port = htons ( iPortNumber ); - bSuccess = ( ::bind ( UdpSocket , - (sockaddr*) &UdpSocketIn6Addr, - sizeof ( struct sockaddr_in6 ) ) == 0 ); - } else { - UdpSocketInAddr.sin_port = htons ( iPortNumber ); - bSuccess = ( ::bind ( UdpSocket , - (sockaddr*) &UdpSocketInAddr, - sizeof ( sockaddr_in ) ) == 0 ); - } + *UdpPort = htons ( iPortNumber ); + + bSuccess = ( ::bind ( UdpSocket, &UdpSocketAddr.sa, UdpSocketAddrLen ) == 0 ); } if ( !bSuccess ) { // we cannot bind socket, throw error - throw CGenErr ( "Cannot bind the socket (maybe " - "the software is already running).", - "Network Error" ); + throw CGenErr ( "Cannot bind the socket (maybe the software is already running).", "Network Error" ); } // Connections ------------------------------------------------------------- @@ -228,6 +227,10 @@ CSocket::~CSocket() void CSocket::SendPacket ( const CVector& vecbySendBuf, const CHostAddress& HostAddr ) { + uSockAddr UdpSocketAddr; + + memset ( &UdpSocketAddr, 0, sizeof ( UdpSocketAddr ) ); + QMutexLocker locker ( &Mutex ); const int iVecSizeOut = vecbySendBuf.Size(); @@ -238,34 +241,57 @@ void CSocket::SendPacket ( const CVector& vecbySendBuf, const CHostAddr // char vector in "const char*", for this we first convert the const // uint8_t vector in a read/write uint8_t vector and then do the cast to // const char *) - if (HostAddr.InetAddr.protocol() == QAbstractSocket::IPv4Protocol) { - - sockaddr_in UdpSocketOutAddr; - UdpSocketOutAddr.sin_family = AF_INET; - UdpSocketOutAddr.sin_port = htons ( HostAddr.iPort ); - UdpSocketOutAddr.sin_addr.s_addr = htonl ( HostAddr.InetAddr.toIPv4Address() ); - - sendto ( UdpSocket, - (const char*) &( (CVector) vecbySendBuf )[0], - iVecSizeOut, - 0, - (sockaddr*) &UdpSocketOutAddr, - sizeof ( UdpSocketOutAddr ) ); - - } else { - - sockaddr_in6 UdpSocketOutAddr; - - UdpSocketOutAddr.sin6_family = AF_INET6; - UdpSocketOutAddr.sin6_port = htons ( HostAddr.iPort ); - inet_pton(AF_INET6, HostAddr.InetAddr.toString().toLocal8Bit().constData(), &UdpSocketOutAddr.sin6_addr); + if ( HostAddr.InetAddr.protocol() == QAbstractSocket::IPv4Protocol ) + { + if ( bEnableIPv6 ) + { + // Linux and Mac allow to pass an AF_INET address to a dual-stack socket, + // but Windows does not. So use a V4MAPPED address in an AF_INET6 sockaddr, + // which works on all platforms. + + UdpSocketAddr.sa6.sin6_family = AF_INET6; + UdpSocketAddr.sa6.sin6_port = htons ( HostAddr.iPort ); + + uint32_t* addr = (uint32_t*) &UdpSocketAddr.sa6.sin6_addr; + + addr[0] = 0; + addr[1] = 0; + addr[2] = htonl ( 0xFFFF ); + addr[3] = htonl ( HostAddr.InetAddr.toIPv4Address() ); + + sendto ( UdpSocket, + (const char*) &( (CVector) vecbySendBuf )[0], + iVecSizeOut, + 0, + &UdpSocketAddr.sa, + sizeof ( UdpSocketAddr.sa6 ) ); + } + else + { + UdpSocketAddr.sa4.sin_family = AF_INET; + UdpSocketAddr.sa4.sin_port = htons ( HostAddr.iPort ); + UdpSocketAddr.sa4.sin_addr.s_addr = htonl ( HostAddr.InetAddr.toIPv4Address() ); + + sendto ( UdpSocket, + (const char*) &( (CVector) vecbySendBuf )[0], + iVecSizeOut, + 0, + &UdpSocketAddr.sa, + sizeof ( UdpSocketAddr.sa4 ) ); + } + } + else if ( bEnableIPv6 ) + { + UdpSocketAddr.sa6.sin6_family = AF_INET6; + UdpSocketAddr.sa6.sin6_port = htons ( HostAddr.iPort ); + inet_pton ( AF_INET6, HostAddr.InetAddr.toString().toLocal8Bit().constData(), &UdpSocketAddr.sa6.sin6_addr ); sendto ( UdpSocket, (const char*) &( (CVector) vecbySendBuf )[0], iVecSizeOut, 0, - (sockaddr*) &UdpSocketOutAddr, - sizeof ( UdpSocketOutAddr ) ); + &UdpSocketAddr.sa, + sizeof ( UdpSocketAddr.sa6 ) ); } } } @@ -296,19 +322,14 @@ void CSocket::OnDataReceived() */ // read block from network interface and query address of sender - struct sockaddr_storage addrstorage; + uSockAddr UdpSocketAddr; #ifdef _WIN32 - int SenderAddrSize = sizeof ( struct sockaddr_storage ); + int SenderAddrSize = sizeof ( UdpSocketAddr ); #else - socklen_t SenderAddrSize = sizeof ( struct sockaddr_storage ); + socklen_t SenderAddrSize = sizeof ( UdpSocketAddr ); #endif - const long iNumBytesRead = recvfrom ( UdpSocket, - (char*) &vecbyRecBuf[0], - MAX_SIZE_BYTES_NETW_BUF, - 0, - (sockaddr*) &addrstorage, - &SenderAddrSize ); + const long iNumBytesRead = recvfrom ( UdpSocket, (char*) &vecbyRecBuf[0], MAX_SIZE_BYTES_NETW_BUF, 0, &UdpSocketAddr.sa, &SenderAddrSize ); // check if an error occurred or no data could be read if ( iNumBytesRead <= 0 ) @@ -316,19 +337,24 @@ void CSocket::OnDataReceived() return; } - if ( addrstorage.ss_family == AF_INET6) { - struct sockaddr_in6 *in6 = reinterpret_cast(&addrstorage); - if (IN6_IS_ADDR_V4MAPPED(&(in6->sin6_addr))) { - RecHostAddr.InetAddr.setAddress ( ntohl (*((uint32_t*)in6->sin6_addr.s6_addr + 3) ) ); - } else { - RecHostAddr.InetAddr.setAddress( in6->sin6_addr.s6_addr ); + if ( UdpSocketAddr.sa.sa_family == AF_INET6 ) + { + if ( IN6_IS_ADDR_V4MAPPED ( &( UdpSocketAddr.sa6.sin6_addr ) ) ) + { + const uint32_t addr = ( (const uint32_t*) ( &( UdpSocketAddr.sa6.sin6_addr ) ) )[3]; + RecHostAddr.InetAddr.setAddress ( ntohl ( addr ) ); + } + else + { + RecHostAddr.InetAddr.setAddress ( UdpSocketAddr.sa6.sin6_addr.s6_addr ); } - RecHostAddr.iPort = ntohs ( in6->sin6_port ); - } else { - struct sockaddr_in *in = reinterpret_cast(&addrstorage); + RecHostAddr.iPort = ntohs ( UdpSocketAddr.sa6.sin6_port ); + } + else + { // convert address of client - RecHostAddr.InetAddr.setAddress ( ntohl ( in->sin_addr.s_addr ) ); - RecHostAddr.iPort = ntohs ( in->sin_port ); + RecHostAddr.InetAddr.setAddress ( ntohl ( UdpSocketAddr.sa4.sin_addr.s_addr ) ); + RecHostAddr.iPort = ntohs ( UdpSocketAddr.sa4.sin_port ); } // check if this is a protocol message diff --git a/src/socket.h b/src/socket.h index bbf6c49880..d41a343881 100644 --- a/src/socket.h +++ b/src/socket.h @@ -53,18 +53,20 @@ class CSocket : public QObject Q_OBJECT public: - CSocket ( CChannel* pNewChannel, const quint16 iPortNumber, const quint16 iQosNumber, const QString& strServerBindIP ) : + CSocket ( CChannel* pNewChannel, const quint16 iPortNumber, const quint16 iQosNumber, const QString& strServerBindIP, bool bEnableIPv6 ) : pChannel ( pNewChannel ), bIsClient ( true ), - bJitterBufferOK ( true ) + bJitterBufferOK ( true ), + bEnableIPv6 ( bEnableIPv6 ) { Init ( iPortNumber, iQosNumber, strServerBindIP ); } - CSocket ( CServer* pNServP, const quint16 iPortNumber, const quint16 iQosNumber, const QString& strServerBindIP ) : + CSocket ( CServer* pNServP, const quint16 iPortNumber, const quint16 iQosNumber, const QString& strServerBindIP, bool bEnableIPv6 ) : pServer ( pNServP ), bIsClient ( false ), - bJitterBufferOK ( true ) + bJitterBufferOK ( true ), + bEnableIPv6 ( bEnableIPv6 ) { Init ( iPortNumber, iQosNumber, strServerBindIP ); } @@ -99,6 +101,8 @@ class CSocket : public QObject bool bJitterBufferOK; + bool bEnableIPv6; + public: void OnDataReceived(); @@ -127,20 +131,20 @@ class CHighPrioSocket : public QObject Q_OBJECT public: - CHighPrioSocket ( CChannel* pNewChannel, const quint16 iPortNumber, const quint16 iQosNumber, const QString& strServerBindIP ) : - Socket ( pNewChannel, iPortNumber, iQosNumber, strServerBindIP ) + CHighPrioSocket ( CChannel* pNewChannel, const quint16 iPortNumber, const quint16 iQosNumber, const QString& strServerBindIP, bool bEnableIPv6 ) : + Socket ( pNewChannel, iPortNumber, iQosNumber, strServerBindIP, bEnableIPv6 ) { Init(); } - CHighPrioSocket ( CChannel* pNewChannel, const quint16 iPortNumber, const quint16 iQosNumber ) : - Socket ( pNewChannel, iPortNumber, iQosNumber, "" ) + CHighPrioSocket ( CChannel* pNewChannel, const quint16 iPortNumber, const quint16 iQosNumber, bool bEnableIPv6 ) : + Socket ( pNewChannel, iPortNumber, iQosNumber, "", bEnableIPv6 ) { Init(); } - CHighPrioSocket ( CServer* pNewServer, const quint16 iPortNumber, const quint16 iQosNumber, const QString& strServerBindIP ) : - Socket ( pNewServer, iPortNumber, iQosNumber, strServerBindIP ) + CHighPrioSocket ( CServer* pNewServer, const quint16 iPortNumber, const quint16 iQosNumber, const QString& strServerBindIP, bool bEnableIPv6 ) : + Socket ( pNewServer, iPortNumber, iQosNumber, strServerBindIP, bEnableIPv6 ) { Init(); } @@ -222,3 +226,11 @@ class CHighPrioSocket : public QObject signals: void InvalidPacketReceived ( CHostAddress RecHostAddr ); }; + +// overlay generic, IPv4 and IPv6 sockaddr structures +typedef union +{ + struct sockaddr sa; + struct sockaddr_in sa4; + struct sockaddr_in6 sa6; +} uSockAddr; diff --git a/src/util.cpp b/src/util.cpp index 3502a64920..54ac430a81 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -667,76 +667,118 @@ void CLanguageComboBox::OnLanguageActivated ( int iLanguageIdx ) * Other Classes * \******************************************************************************/ // Network utility functions --------------------------------------------------- -bool NetworkUtil::ParseNetworkAddress ( QString strAddress, CHostAddress& HostAddress ) +bool NetworkUtil::ParseNetworkAddress ( QString strAddress, CHostAddress& HostAddress, bool bEnableIPv6 ) { QHostAddress InetAddr; - quint16 iNetPort = DEFAULT_PORT_NUMBER; + unsigned int iNetPort = DEFAULT_PORT_NUMBER; + + // qInfo() << qUtf8Printable ( QString ( "Parsing network address %1" ).arg ( strAddress ) ); // init requested host address with invalid address first HostAddress = CHostAddress(); - // parse input address for the type "IP4 address:port number" or - // "[IP6 address]:port number" assuming the syntax is correctly given - QStringList slAddress = strAddress.split ( ":" ); - QString strSep = ":"; - bool bIsIP6 = false; + // Allow the following address formats: + // [addr4or6] + // [addr4or6]:port + // addr4 + // addr4:port + // hostname + // hostname:port + // (where addr4or6 is a literal IPv4 or IPv6 address, and addr4 is a literal IPv4 address + + bool bLiteralAddr = false; + QRegExp rx1 ( "^\\[([^]]*)\\](?::(\\d+))?$" ); // [addr4or6] or [addr4or6]:port + QRegExp rx2 ( "^([^:]*)(?::(\\d+))?$" ); // addr4 or addr4:port or host or host:port + + QString strPort; - if ( slAddress.count() > 2 ) + // parse input address with rx1 and rx2 in turn, capturing address/host and port + if ( rx1.indexIn ( strAddress ) == 0 ) { - // IP6 address - bIsIP6 = true; - strSep = "]:"; + // literal address within [] + strAddress = rx1.cap ( 1 ); + strPort = rx1.cap ( 2 ); + bLiteralAddr = true; // don't allow hostname within [] + } + else if ( rx2.indexIn ( strAddress ) == 0 ) + { + // hostname or IPv4 address + strAddress = rx2.cap ( 1 ); + strPort = rx2.cap ( 2 ); + } + else + { + // invalid format + // qInfo() << qUtf8Printable ( QString ( "Invalid address format" ) ); + return false; } - - QString strPort = strAddress.section ( strSep, 1, 1 ); if ( !strPort.isEmpty() ) { - // a colon is present in the address string, try to extract port number + // a port number was given: extract port number iNetPort = strPort.toInt(); - // extract address port before separator (should be actual internet address) - strAddress = strAddress.section ( strSep, 0, 0 ); - - if ( bIsIP6 ) + if ( iNetPort >= 65536 ) { - // remove "[" at the beginning - strAddress.remove ( 0, 1 ); + // invalid port number + // qInfo() << qUtf8Printable ( QString ( "Invalid port number specified" ) ); + return false; } } // first try if this is an IP number an can directly applied to QHostAddress - if ( !InetAddr.setAddress ( strAddress ) ) + if ( InetAddr.setAddress ( strAddress ) ) { - // it was no valid IP address, try to get host by name, assuming + if ( !bEnableIPv6 && InetAddr.protocol() == QAbstractSocket::IPv6Protocol ) + { + // do not allow IPv6 addresses if not enabled + // qInfo() << qUtf8Printable ( QString ( "IPv6 addresses disabled" ) ); + return false; + } + } + else + { + // it was no valid IP address. If literal required, return as invalid + if ( bLiteralAddr ) + { + // qInfo() << qUtf8Printable ( QString ( "Invalid literal IP address" ) ); + 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 } - // use the first IPv4 address, if any - bool bFoundIPv4 = false; + bool bFoundAddr = false; foreach ( const QHostAddress HostAddr, HostInfo.addresses() ) { - if ( HostAddr.protocol() == QAbstractSocket::IPv4Protocol ) + // 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; - bFoundIPv4 = true; + bFoundAddr = true; break; } } - if ( !bFoundIPv4 ) + if ( !bFoundAddr ) { - // only found IPv6 addresses + // no valid address found + // qInfo() << qUtf8Printable ( QString ( "No IP address found for hostname" ) ); return false; } } + // qInfo() << qUtf8Printable ( QString ( "Parsed network address %1" ).arg ( InetAddr.toString() ) ); + HostAddress = CHostAddress ( InetAddr, iNetPort ); return true; @@ -762,6 +804,26 @@ CHostAddress NetworkUtil::GetLocalAddress() } } +CHostAddress NetworkUtil::GetLocalAddress6() +{ + QUdpSocket socket; + // As we are using UDP, the connectToHost() does not generate any traffic at all. + // We just require a socket which is pointed towards the Internet in + // order to find out the IP of our own external interface: + socket.connectToHost ( WELL_KNOWN_HOST6, WELL_KNOWN_PORT ); + + if ( socket.waitForConnected ( IP_LOOKUP_TIMEOUT ) ) + { + return CHostAddress ( socket.localAddress(), 0 ); + } + else + { + qWarning() << "could not determine local IPv6 address:" << socket.errorString() << "- using localhost"; + + return CHostAddress ( QHostAddress::LocalHostIPv6, 0 ); + } +} + QString NetworkUtil::GetCentralServerAddress ( const ECSAddType eCentralServerAddressType, const QString& strCentralServerAddress ) { switch ( eCentralServerAddressType ) diff --git a/src/util.h b/src/util.h index 8fac9a931c..285ab1ab31 100644 --- a/src/util.h +++ b/src/util.h @@ -986,10 +986,11 @@ class CNetworkTransportProps class NetworkUtil { public: - static bool ParseNetworkAddress ( QString strAddress, CHostAddress& HostAddress ); + static bool ParseNetworkAddress ( QString strAddress, CHostAddress& HostAddress, bool bEnableIPv6 ); static QString FixAddress ( const QString& strAddress ); static CHostAddress GetLocalAddress(); + static CHostAddress GetLocalAddress6(); static QString GetCentralServerAddress ( const ECSAddType eCentralServerAddressType, const QString& strCentralServerAddress ); static bool IsPrivateNetworkIP ( const QHostAddress& qhAddr ); };