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 94d254920f..d7cdb9a699 100644 --- a/src/socket.cpp +++ b/src/socket.cpp @@ -25,9 +25,21 @@ #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 ) { + uSockAddr UdpSocketAddr; + + int UdpSocketAddrLen; + uint16_t* UdpPort; + #ifdef _WIN32 // for the Windows socket usage we have to start it up first @@ -39,27 +51,72 @@ void CSocket::Init ( const quint16 iPortNumber, const quint16 iQosNumber, const WSAStartup ( MAKEWORD ( 1, 0 ), &wsa ); #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 ) ); + memset ( &UdpSocketAddr, 0, sizeof ( UdpSocketAddr ) ); - // 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() ) + if ( bEnableIPv6 ) { - UdpSocketInAddr.sin_addr.s_addr = INADDR_ANY; + // 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 ) ); + + // set the QoS + const char tos = (char) iQosNumber; // Quality of Service + setsockopt ( UdpSocket, IPPROTO_IPV6, IPV6_TCLASS, &tos, sizeof ( tos ) ); + + UdpSocketAddr.sa6.sin6_family = AF_INET6; + UdpSocketAddr.sa6.sin6_addr = in6addr_any; + UdpSocketAddrLen = sizeof ( UdpSocketAddr.sa6 ); + + UdpPort = &UdpSocketAddr.sa6.sin6_port; // where to put the port number + + // 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() ) + //{ + // 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() ); + } } + // allocate memory for network receive and send buffer in samples + vecbyRecBuf.Init ( MAX_SIZE_BYTES_NETW_BUF ); + // initialize the listening socket bool bSuccess; @@ -68,9 +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 - UdpSocketInAddr.sin_port = htons ( 0 ); + *UdpPort = htons ( 0 ); - bSuccess = ( ::bind ( UdpSocket, (sockaddr*) &UdpSocketInAddr, sizeof ( sockaddr_in ) ) == 0 ); + bSuccess = ( ::bind ( UdpSocket, &UdpSocketAddr.sa, UdpSocketAddrLen ) == 0 ); } else { @@ -86,9 +143,9 @@ void CSocket::Init ( const quint16 iPortNumber, const quint16 iQosNumber, const while ( !bSuccess && ( iClientPortIncrement <= NUM_SOCKET_PORTS_TO_TRY ) ) { - UdpSocketInAddr.sin_port = htons ( startingPortNumber + iClientPortIncrement ); + *UdpPort = htons ( startingPortNumber + iClientPortIncrement ); - bSuccess = ( ::bind ( UdpSocket, (sockaddr*) &UdpSocketInAddr, sizeof ( sockaddr_in ) ) == 0 ); + bSuccess = ( ::bind ( UdpSocket, &UdpSocketAddr.sa, UdpSocketAddrLen ) == 0 ); iClientPortIncrement++; } @@ -99,17 +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 - UdpSocketInAddr.sin_port = htons ( iPortNumber ); + *UdpPort = htons ( iPortNumber ); - bSuccess = ( ::bind ( UdpSocket, (sockaddr*) &UdpSocketInAddr, sizeof ( sockaddr_in ) ) == 0 ); + 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 ------------------------------------------------------------- @@ -172,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(); @@ -181,19 +240,59 @@ 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 ) + { + 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, + &UdpSocketAddr.sa, + sizeof ( UdpSocketAddr.sa6 ) ); + } } } @@ -223,14 +322,14 @@ void CSocket::OnDataReceived() */ // read block from network interface and query address of sender - sockaddr_in SenderAddr; + uSockAddr UdpSocketAddr; #ifdef _WIN32 - int SenderAddrSize = sizeof ( sockaddr_in ); + int SenderAddrSize = sizeof ( UdpSocketAddr ); #else - socklen_t SenderAddrSize = sizeof ( sockaddr_in ); + socklen_t SenderAddrSize = sizeof ( UdpSocketAddr ); #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, &UdpSocketAddr.sa, &SenderAddrSize ); // check if an error occurred or no data could be read if ( iNumBytesRead <= 0 ) @@ -238,9 +337,25 @@ void CSocket::OnDataReceived() return; } - // convert address of client - RecHostAddr.InetAddr.setAddress ( ntohl ( SenderAddr.sin_addr.s_addr ) ); - RecHostAddr.iPort = ntohs ( SenderAddr.sin_port ); + 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 ( UdpSocketAddr.sa6.sin6_port ); + } + else + { + // convert address of client + RecHostAddr.InetAddr.setAddress ( ntohl ( UdpSocketAddr.sa4.sin_addr.s_addr ) ); + RecHostAddr.iPort = ntohs ( UdpSocketAddr.sa4.sin_port ); + } // check if this is a protocol message int iRecCounter; 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 ); };