diff --git a/.github/workflows/samples.yaml b/.github/workflows/samples.yaml index c1815230bb..2bf8288ae8 100644 --- a/.github/workflows/samples.yaml +++ b/.github/workflows/samples.yaml @@ -17,6 +17,10 @@ jobs: env: AWS_KVS_LOG_LEVEL: 2 + strategy: + matrix: + endpoint-type: [ "legacy", "dual-stack" ] + permissions: id-token: write contents: read @@ -36,6 +40,15 @@ jobs: role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }} aws-region: ${{ secrets.AWS_REGION }} + - name: Set endpoint type + run: | + if [ "${{ matrix.endpoint-type }}" = "dual-stack" ]; then + echo "Using dual-stack endpoints" + echo "USE_DUAL_STACK_ENDPOINTS=ON" >> $GITHUB_ENV + else + echo "Using legacy endpoints" + fi + - name: Build repository run: | mkdir build && cd build diff --git a/CMake/Dependencies/libkvsCommonLws-CMakeLists.txt b/CMake/Dependencies/libkvsCommonLws-CMakeLists.txt index 5cd8f1df67..520a68c525 100644 --- a/CMake/Dependencies/libkvsCommonLws-CMakeLists.txt +++ b/CMake/Dependencies/libkvsCommonLws-CMakeLists.txt @@ -6,7 +6,7 @@ include(ExternalProject) ExternalProject_Add(libkvsCommonLws-download GIT_REPOSITORY https://github.com/awslabs/amazon-kinesis-video-streams-producer-c.git - GIT_TAG v1.5.4 + GIT_TAG v1.6.0 GIT_PROGRESS TRUE GIT_SHALLOW TRUE PREFIX ${CMAKE_CURRENT_BINARY_DIR}/build diff --git a/CMake/Dependencies/libwebsockets-CMakeLists.txt b/CMake/Dependencies/libwebsockets-CMakeLists.txt index c040ea2dda..e45210843c 100644 --- a/CMake/Dependencies/libwebsockets-CMakeLists.txt +++ b/CMake/Dependencies/libwebsockets-CMakeLists.txt @@ -63,6 +63,7 @@ ExternalProject_Add(project_libwebsockets -DLWS_EXT_PTHREAD_LIBRARIES=${LWS_EXT_PTHREAD_LIBRARIES} -DLWS_OPENSSL_INCLUDE_DIRS=${LWS_OPENSSL_INCLUDE_DIRS} -DLWS_OPENSSL_LIBRARIES=${LWS_OPENSSL_LIBRARIES} + -DLWS_WITH_IPV6=ON BUILD_ALWAYS TRUE TEST_COMMAND "" ) diff --git a/README.md b/README.md index 1f2e1c2fc9..9083a87769 100644 --- a/README.md +++ b/README.md @@ -573,6 +573,8 @@ Similar to the heap profile, you only need to specify the following environment More information about what environment variables you can configure can be found [here](https://gperftools.github.io/gperftools/cpuprofile.html) +## Additional Features + ### Filtering network interfaces This is useful to reduce candidate gathering time when it is known for certain network interfaces to not work well. A sample callback is available in `Common.c`. The `iceSetInterfaceFilterFunc` in `KvsRtcConfiguration` must be set to the required callback. In the sample, it can be done this way in `initializePeerConnection()`: @@ -632,6 +634,20 @@ The SDK enables generating these stats by default. To control whether the SDK ca `configuration.kvsRtcConfiguration.enableIceStats = FALSE`. Disabling these stats may lead to reductions in memory use. +### Enabling dual-stack mode +To use dual-stack AWS KVS endpoints and attempt to gather IPv6 ICE candidates, set the following environment variable: +``` +export KVS_DUALSTACK_ENDPOINTS=ON +``` + +In dual-stack mode, ICE gathering will attempt to include IPv6 candidates, but compatibility ultimately depends on the local network configuration and the capabilities of the receiving peers. + + +To disable dual-stack mode, unset the environment variable: +``` +unset KVS_DUALSTACK_ENDPOINTS +``` + ## Documentation All Public APIs are documented in our [Include.h](https://github.com/awslabs/amazon-kinesis-video-streams-webrtc-sdk-c/blob/main/src/include/com/amazonaws/kinesis/video/webrtcclient/Include.h), we also generate a [Doxygen](https://awslabs.github.io/amazon-kinesis-video-streams-webrtc-sdk-c/) each commit for easier navigation. diff --git a/samples/Common.c b/samples/Common.c index b371f30779..a3e2f7d7f1 100644 --- a/samples/Common.c +++ b/samples/Common.c @@ -398,6 +398,8 @@ STATUS initializePeerConnection(PSampleConfiguration pSampleConfiguration, PRtcP * It's recommended to not pass too many TURN iceServers to configuration because it will slow down ice gathering in non-trickle mode. */ + DLOGD("TURN server %d urls: %s", j + 1, pIceConfigInfo->uris[j]); + STRNCPY(configuration.iceServers[uriCount + 1].urls, pIceConfigInfo->uris[j], MAX_ICE_CONFIG_URI_LEN); STRNCPY(configuration.iceServers[uriCount + 1].credential, pIceConfigInfo->password, MAX_ICE_CONFIG_CREDENTIAL_LEN); STRNCPY(configuration.iceServers[uriCount + 1].username, pIceConfigInfo->userName, MAX_ICE_CONFIG_USER_NAME_LEN); diff --git a/src/include/com/amazonaws/kinesis/video/webrtcclient/Include.h b/src/include/com/amazonaws/kinesis/video/webrtcclient/Include.h index 3fd3a3711f..29dc5d2517 100644 --- a/src/include/com/amazonaws/kinesis/video/webrtcclient/Include.h +++ b/src/include/com/amazonaws/kinesis/video/webrtcclient/Include.h @@ -475,6 +475,11 @@ extern "C" { */ #define MAX_SIGNALING_ENDPOINT_URI_LEN 512 +/** + * Maximum allowed Control Plane URI length + */ +#define MAX_CONTROL_PLANE_URI_CHAR_LEN 256 + /** * Maximum allowed correlation ID length */ diff --git a/src/source/Ice/IceAgent.c b/src/source/Ice/IceAgent.c index a9d3a066a8..15a9796339 100644 --- a/src/source/Ice/IceAgent.c +++ b/src/source/Ice/IceAgent.c @@ -116,7 +116,8 @@ STATUS createIceAgent(PCHAR username, PCHAR password, PIceAgentCallbacks pIceAge if (doStatCalcs) { CHK(NULL != (pIceAgent->pRtcIceServerDiagnostics[i] = (PRtcIceServerDiagnostics) MEMCALLOC(1, SIZEOF(RtcIceServerDiagnostics))), STATUS_NOT_ENOUGH_MEMORY); - pIceAgent->pRtcIceServerDiagnostics[i]->port = (INT32) getInt16(pIceAgent->iceServers[i].ipAddress.port); + pIceAgent->pRtcIceServerDiagnostics[i]->port = (INT32) getInt16(pIceAgent->iceServers[i].ipAddresses.ipv4Address.port); + // TODO: How to handle ICE server diagnostis for dual-stack case?... switch (pIceAgent->iceServers[pIceAgent->iceServersCount].transport) { case KVS_SOCKET_PROTOCOL_UDP: STRCPY(pIceAgent->pRtcIceServerDiagnostics[i]->protocol, ICE_TRANSPORT_TYPE_UDP); @@ -1440,6 +1441,7 @@ STATUS iceAgentSendSrflxCandidateRequest(PIceAgent pIceAgent) PIceServer pIceServer = NULL; PStunPacket pBindingRequest = NULL; UINT64 checkSum = 0; + PKvsIpAddress pStunServerAddr = NULL; CHK(pIceAgent != NULL, STATUS_NULL_ARG); // Assume holding pIceAgent->lock @@ -1458,18 +1460,30 @@ STATUS iceAgentSendSrflxCandidateRequest(PIceAgent pIceAgent) switch (pCandidate->iceCandidateType) { case ICE_CANDIDATE_TYPE_SERVER_REFLEXIVE: pIceServer = &(pIceAgent->iceServers[pCandidate->iceServerIndex]); - if (pIceServer->ipAddress.family == pCandidate->ipAddress.family) { - // update transactionId - CHK_STATUS( - iceUtilsGenerateTransactionId(pBindingRequest->header.transactionId, ARRAY_SIZE(pBindingRequest->header.transactionId))); - - transactionIdStoreInsert(pIceAgent->pStunBindingRequestTransactionIdStore, pBindingRequest->header.transactionId); - checkSum = COMPUTE_CRC32(pBindingRequest->header.transactionId, ARRAY_SIZE(pBindingRequest->header.transactionId)); - CHK_STATUS(iceAgentSendStunPacket(pBindingRequest, NULL, 0, pIceAgent, pCandidate, &pIceServer->ipAddress)); - if (pIceAgent->pRtcIceServerDiagnostics[pCandidate->iceServerIndex] != NULL) { - pIceAgent->pRtcIceServerDiagnostics[pCandidate->iceServerIndex]->totalRequestsSent++; - CHK_STATUS(hashTableUpsert(pIceAgent->requestTimestampDiagnostics, checkSum, GETTIME())); - } + + if (pIceServer->ipAddresses.ipv4Address.family != KVS_IP_FAMILY_TYPE_NOT_SET && + pCandidate->ipAddress.family == KVS_IP_FAMILY_TYPE_IPV4) { + pStunServerAddr = &pIceServer->ipAddresses.ipv4Address; + } else if (pIceServer->ipAddresses.ipv6Address.family != KVS_IP_FAMILY_TYPE_NOT_SET && + pCandidate->ipAddress.family == KVS_IP_FAMILY_TYPE_IPV6) { + pStunServerAddr = &pIceServer->ipAddresses.ipv6Address; + } + CHK_ERR(pStunServerAddr != NULL, STATUS_INVALID_ARG, "No IP-family-compatible STUN server address found for candidate %s", + pCandidate->id); + + // update transactionId + CHK_STATUS( + iceUtilsGenerateTransactionId(pBindingRequest->header.transactionId, ARRAY_SIZE(pBindingRequest->header.transactionId))); + + transactionIdStoreInsert(pIceAgent->pStunBindingRequestTransactionIdStore, pBindingRequest->header.transactionId); + checkSum = COMPUTE_CRC32(pBindingRequest->header.transactionId, ARRAY_SIZE(pBindingRequest->header.transactionId)); + + DLOGD("Sending STUN binding request to STUN server: %u:%u", pStunServerAddr->address, pStunServerAddr->port); + + CHK_STATUS(iceAgentSendStunPacket(pBindingRequest, NULL, 0, pIceAgent, pCandidate, &pIceServer->ipAddresses.ipv4Address)); + if (pIceAgent->pRtcIceServerDiagnostics[pCandidate->iceServerIndex] != NULL) { + pIceAgent->pRtcIceServerDiagnostics[pCandidate->iceServerIndex]->totalRequestsSent++; + CHK_STATUS(hashTableUpsert(pIceAgent->requestTimestampDiagnostics, checkSum, GETTIME())); } break; @@ -1762,7 +1776,15 @@ STATUS iceAgentInitSrflxCandidate(PIceAgent pIceAgent) if (pCandidate->iceCandidateType == ICE_CANDIDATE_TYPE_HOST) { for (j = 0; j < pIceAgent->iceServersCount; j++) { pIceServer = &pIceAgent->iceServers[j]; - if (!pIceServer->isTurn && pIceServer->ipAddress.family == pCandidate->ipAddress.family) { + + if (pCandidate->ipAddress.family == KVS_IP_FAMILY_TYPE_NOT_SET) { + DLOGW("Skipping local host candidate %s with unset IP family for srflx candidate generation", pCandidate->id); + continue; + } + + if (!pIceServer->isTurn && + (pIceServer->ipAddresses.ipv4Address.family == pCandidate->ipAddress.family || + pIceServer->ipAddresses.ipv6Address.family == pCandidate->ipAddress.family)) { CHK((pNewCandidate = (PIceCandidate) MEMCALLOC(1, SIZEOF(IceCandidate))) != NULL, STATUS_NOT_ENOUGH_MEMORY); generateJSONSafeString(pNewCandidate->id, ARRAY_SIZE(pNewCandidate->id)); pNewCandidate->isRemote = FALSE; @@ -1792,20 +1814,20 @@ STATUS iceAgentInitSrflxCandidate(PIceAgent pIceAgent) // Create and start the connection listener outside of the locks for (j = 0; j < srflxCount; j++) { pCandidate = srflxCandidates[j]; - // TODO: IPv6 STUN is not supported at the moment. Remove this check if the support is added in the future if (IS_IPV4_ADDR(&(pCandidate->ipAddress))) { - // open up a new socket at host candidate's ip address for server reflex candidate. - // the new port will be stored in pNewCandidate->ipAddress.port. And the Ip address will later be updated - // with the correct ip address once the STUN response is received. - CHK_STATUS(createSocketConnection(pCandidate->ipAddress.family, KVS_SOCKET_PROTOCOL_UDP, &pCandidate->ipAddress, NULL, (UINT64) pIceAgent, - incomingDataHandler, pIceAgent->kvsRtcConfiguration.sendBufSize, &pCandidate->pSocketConnection)); - ATOMIC_STORE_BOOL(&pCandidate->pSocketConnection->receiveData, TRUE); - // connectionListener will free the pSocketConnection at the end. - CHK_STATUS(connectionListenerAddConnection(pIceAgent->pConnectionListener, pCandidate->pSocketConnection)); - + DLOGI("Initializing an IPv4 STUN candidate..."); } else { - DLOGW("IPv6 candidate detected, ignoring...."); + DLOGI("Initializing an IPv6 STUN candidate..."); } + + // open up a new socket at host candidate's ip address for server reflex candidate. + // the new port will be stored in pNewCandidate->ipAddress.port. And the Ip address will later be updated + // with the correct ip address once the STUN response is received. + CHK_STATUS(createSocketConnection(pCandidate->ipAddress.family, KVS_SOCKET_PROTOCOL_UDP, &pCandidate->ipAddress, NULL, (UINT64) pIceAgent, + incomingDataHandler, pIceAgent->kvsRtcConfiguration.sendBufSize, &pCandidate->pSocketConnection)); + ATOMIC_STORE_BOOL(&pCandidate->pSocketConnection->receiveData, TRUE); + // connectionListener will free the pSocketConnection at the end. + CHK_STATUS(connectionListenerAddConnection(pIceAgent->pConnectionListener, pCandidate->pSocketConnection)); } CleanUp: @@ -1835,12 +1857,27 @@ STATUS iceAgentInitRelayCandidates(PIceAgent pIceAgent) CHK(pIceAgent != NULL, STATUS_NULL_ARG); for (j = 0; j < pIceAgent->iceServersCount; j++) { if (pIceAgent->iceServers[j].isTurn) { - if (pIceAgent->iceServers[j].transport == KVS_SOCKET_PROTOCOL_UDP || pIceAgent->iceServers[j].transport == KVS_SOCKET_PROTOCOL_NONE) { - CHK_STATUS(iceAgentInitRelayCandidate(pIceAgent, j, KVS_SOCKET_PROTOCOL_UDP)); + DLOGD("Initializing TURN relay candidates for ICE server %u with IPv4 family %u and IPv6 family (if available) %u", j, + pIceAgent->iceServers[j].ipAddresses.ipv4Address.family, pIceAgent->iceServers[j].ipAddresses.ipv6Address.family); + + if (pIceAgent->iceServers[j].ipAddresses.ipv4Address.family != KVS_IP_FAMILY_TYPE_NOT_SET) { + if (pIceAgent->iceServers[j].transport == KVS_SOCKET_PROTOCOL_UDP || pIceAgent->iceServers[j].transport == KVS_SOCKET_PROTOCOL_NONE) { + CHK_STATUS(iceAgentInitRelayCandidate(pIceAgent, j, KVS_SOCKET_PROTOCOL_UDP, KVS_IP_FAMILY_TYPE_IPV4)); + } + + if (pIceAgent->iceServers[j].transport == KVS_SOCKET_PROTOCOL_TCP || pIceAgent->iceServers[j].transport == KVS_SOCKET_PROTOCOL_NONE) { + CHK_STATUS(iceAgentInitRelayCandidate(pIceAgent, j, KVS_SOCKET_PROTOCOL_TCP, KVS_IP_FAMILY_TYPE_IPV4)); + } } - if (pIceAgent->iceServers[j].transport == KVS_SOCKET_PROTOCOL_TCP || pIceAgent->iceServers[j].transport == KVS_SOCKET_PROTOCOL_NONE) { - CHK_STATUS(iceAgentInitRelayCandidate(pIceAgent, j, KVS_SOCKET_PROTOCOL_TCP)); + if (pIceAgent->iceServers[j].ipAddresses.ipv6Address.family != KVS_IP_FAMILY_TYPE_NOT_SET) { + if (pIceAgent->iceServers[j].transport == KVS_SOCKET_PROTOCOL_UDP || pIceAgent->iceServers[j].transport == KVS_SOCKET_PROTOCOL_NONE) { + CHK_STATUS(iceAgentInitRelayCandidate(pIceAgent, j, KVS_SOCKET_PROTOCOL_UDP, KVS_IP_FAMILY_TYPE_IPV6)); + } + + if (pIceAgent->iceServers[j].transport == KVS_SOCKET_PROTOCOL_TCP || pIceAgent->iceServers[j].transport == KVS_SOCKET_PROTOCOL_NONE) { + CHK_STATUS(iceAgentInitRelayCandidate(pIceAgent, j, KVS_SOCKET_PROTOCOL_TCP, KVS_IP_FAMILY_TYPE_IPV6)); + } } } } @@ -1875,7 +1912,7 @@ STATUS turnStateFailedFn(PSocketConnection pSocketConnection, UINT64 data) return retStatus; } -STATUS iceAgentInitRelayCandidate(PIceAgent pIceAgent, UINT32 iceServerIndex, KVS_SOCKET_PROTOCOL protocol) +STATUS iceAgentInitRelayCandidate(PIceAgent pIceAgent, UINT32 iceServerIndex, KVS_SOCKET_PROTOCOL protocol, KVS_IP_FAMILY_TYPE turnServerIpFamily) { STATUS retStatus = STATUS_SUCCESS; PDoubleListNode pCurNode = NULL; @@ -1883,8 +1920,11 @@ STATUS iceAgentInitRelayCandidate(PIceAgent pIceAgent, UINT32 iceServerIndex, KV PIceCandidate pNewCandidate = NULL, pCandidate = NULL; BOOL locked = FALSE; PTurnConnection pTurnConnection = NULL; + PKvsIpAddress pTurnServerAddress = NULL; CHK(pIceAgent != NULL, STATUS_NULL_ARG); + CHK(turnServerIpFamily != KVS_IP_FAMILY_TYPE_NOT_SET, STATUS_INVALID_ARG); + /* we dont support TURN on DTLS yet. */ CHK(protocol != KVS_SOCKET_PROTOCOL_UDP || !pIceAgent->iceServers[iceServerIndex].isSecure, retStatus); CHK_WARN(pIceAgent->relayCandidateCount < KVS_ICE_MAX_RELAY_CANDIDATE_COUNT, retStatus, @@ -1895,12 +1935,17 @@ STATUS iceAgentInitRelayCandidate(PIceAgent pIceAgent, UINT32 iceServerIndex, KV generateJSONSafeString(pNewCandidate->id, ARRAY_SIZE(pNewCandidate->id)); pNewCandidate->isRemote = FALSE; + if (turnServerIpFamily == KVS_IP_FAMILY_TYPE_IPV4) { + pTurnServerAddress = &pIceAgent->iceServers[iceServerIndex].ipAddresses.ipv4Address; + } else { + pTurnServerAddress = &pIceAgent->iceServers[iceServerIndex].ipAddresses.ipv6Address; + } + // open up a new socket without binding to any host address. The candidate Ip address will later be updated // with the correct relay ip address once the Allocation success response is received. Relay candidate's socket is managed // by TurnConnection struct. - CHK_STATUS(createSocketConnection(KVS_IP_FAMILY_TYPE_IPV4, protocol, NULL, &pIceAgent->iceServers[iceServerIndex].ipAddress, - (UINT64) pNewCandidate, incomingRelayedDataHandler, pIceAgent->kvsRtcConfiguration.sendBufSize, - &pNewCandidate->pSocketConnection)); + CHK_STATUS(createSocketConnection(turnServerIpFamily, protocol, NULL, pTurnServerAddress, (UINT64) pNewCandidate, incomingRelayedDataHandler, + pIceAgent->kvsRtcConfiguration.sendBufSize, &pNewCandidate->pSocketConnection)); // connectionListener will free the pSocketConnection at the end. CHK_STATUS(connectionListenerAddConnection(pIceAgent->pConnectionListener, pNewCandidate->pSocketConnection)); @@ -1917,7 +1962,7 @@ STATUS iceAgentInitRelayCandidate(PIceAgent pIceAgent, UINT32 iceServerIndex, KV CHK_STATUS(createTurnConnection(&pIceAgent->iceServers[iceServerIndex], pIceAgent->timerQueueHandle, TURN_CONNECTION_DATA_TRANSFER_MODE_SEND_INDIDATION, protocol, &callback, pNewCandidate->pSocketConnection, - pIceAgent->pConnectionListener, &pTurnConnection)); + pIceAgent->pConnectionListener, turnServerIpFamily, &pTurnConnection)); pNewCandidate->pIceAgent = pIceAgent; pNewCandidate->pTurnConnection = pTurnConnection; @@ -1935,10 +1980,15 @@ STATUS iceAgentInitRelayCandidate(PIceAgent pIceAgent, UINT32 iceServerIndex, KV pCurNode = pCurNode->pNext; pCandidate = (PIceCandidate) data; - // TODO: Stop skipping IPv6. Since we're allowing IPv6 remote candidates from iceAgentAddRemoteCandidate for host candidates, - // it's possible to have a situation where the turn server uses IPv4 and the remote candidate uses IPv6. - if (IS_IPV4_ADDR(&pCandidate->ipAddress)) { - CHK_STATUS(turnConnectionAddPeer(pTurnConnection, &pCandidate->ipAddress)); + // Add only peers with matching IP family to the TURN connection. + if (turnServerIpFamily == KVS_IP_FAMILY_TYPE_IPV4) { + if (IS_IPV4_ADDR(&pCandidate->ipAddress)) { + CHK_STATUS(turnConnectionAddPeer(pTurnConnection, &pCandidate->ipAddress)); + } + } else { + if (IS_IPV6_ADDR(&pCandidate->ipAddress)) { + CHK_STATUS(turnConnectionAddPeer(pTurnConnection, &pCandidate->ipAddress)); + } } } diff --git a/src/source/Ice/IceAgent.h b/src/source/Ice/IceAgent.h index 8ad364192d..9849f0cf96 100644 --- a/src/source/Ice/IceAgent.h +++ b/src/source/Ice/IceAgent.h @@ -429,7 +429,7 @@ STATUS iceAgentSendStunPacket(PStunPacket, PBYTE, UINT32, PIceAgent, PIceCandida STATUS iceAgentInitHostCandidate(PIceAgent); STATUS iceAgentInitSrflxCandidate(PIceAgent); STATUS iceAgentInitRelayCandidates(PIceAgent); -STATUS iceAgentInitRelayCandidate(PIceAgent, UINT32, KVS_SOCKET_PROTOCOL); +STATUS iceAgentInitRelayCandidate(PIceAgent, UINT32, KVS_SOCKET_PROTOCOL, KVS_IP_FAMILY_TYPE); STATUS iceAgentCheckConnectionStateSetup(PIceAgent); STATUS iceAgentConnectedStateSetup(PIceAgent); diff --git a/src/source/Ice/IceUtils.c b/src/source/Ice/IceUtils.c index a54a9314f6..79afac1b86 100644 --- a/src/source/Ice/IceUtils.c +++ b/src/source/Ice/IceUtils.c @@ -168,6 +168,10 @@ STATUS iceUtilsSendStunPacket(PStunPacket pStunPacket, PBYTE password, UINT32 pa UINT32 stunPacketSize = STUN_PACKET_ALLOCATION_SIZE; BYTE stunPacketBuffer[STUN_PACKET_ALLOCATION_SIZE]; + CHAR ipAddrStr[KVS_IP_ADDRESS_STRING_BUFFER_LEN]; + + CHK_STATUS(getIpAddrStr(pDest, ipAddrStr, ARRAY_SIZE(ipAddrStr))); + CHK_STATUS(iceUtilsPackageStunPacket(pStunPacket, password, passwordLen, stunPacketBuffer, &stunPacketSize)); CHK(pDest != NULL, STATUS_NULL_ARG); switch (pStunPacket->header.stunMessageType) { @@ -221,7 +225,8 @@ STATUS parseIceServer(PIceServer pIceServer, PCHAR url, PCHAR username, PCHAR cr STATUS retStatus = STATUS_SUCCESS; PCHAR separator = NULL, urlNoPrefix = NULL, paramStart = NULL; UINT32 port = ICE_STUN_DEFAULT_PORT; - CHAR addressResolved[KVS_IP_ADDRESS_STRING_BUFFER_LEN + 1] = {'\0'}; + CHAR addressResolvedIPv4[KVS_IP_ADDRESS_STRING_BUFFER_LEN + 1] = {'\0'}; + CHAR addressResolvedIPv6[KVS_IP_ADDRESS_STRING_BUFFER_LEN + 1] = {'\0'}; // username and credential is only mandatory for turn server CHK(url != NULL && pIceServer != NULL, STATUS_NULL_ARG); @@ -264,7 +269,7 @@ STATUS parseIceServer(PIceServer pIceServer, PCHAR url, PCHAR username, PCHAR cr } if (pIceServer->setIpFn != NULL) { - retStatus = pIceServer->setIpFn(0, pIceServer->url, &pIceServer->ipAddress); + retStatus = pIceServer->setIpFn(0, pIceServer->url, &pIceServer->ipAddresses); } // Adding a NULL_ARG check specifically to cover for the case where early STUN @@ -274,12 +279,22 @@ STATUS parseIceServer(PIceServer pIceServer, PCHAR url, PCHAR username, PCHAR cr // Reset the retStatus to ensure the appropriate status code is returned from // getIpWithHostName retStatus = STATUS_SUCCESS; - CHK_STATUS(getIpWithHostName(pIceServer->url, &pIceServer->ipAddress)); + CHK_STATUS(getIpWithHostName(pIceServer->url, &pIceServer->ipAddresses)); } - pIceServer->ipAddress.port = (UINT16) getInt16((INT16) port); - getIpAddrStr(&pIceServer->ipAddress, addressResolved, ARRAY_SIZE(addressResolved)); - DLOGP("ICE Server address for %s: %s", pIceServer->url, addressResolved); + if (pIceServer->ipAddresses.ipv4Address.family != KVS_IP_FAMILY_TYPE_NOT_SET) { + pIceServer->ipAddresses.ipv4Address.port = (UINT16) getInt16((INT16) port); + CHK_STATUS(getIpAddrStr(&pIceServer->ipAddresses.ipv4Address, addressResolvedIPv4, ARRAY_SIZE(addressResolvedIPv4))); + DLOGD("Resolved ICE Server IPv4 address for %s: %s with port: %u", pIceServer->url, addressResolvedIPv4, + pIceServer->ipAddresses.ipv4Address.port); + } + + if (pIceServer->ipAddresses.ipv6Address.family != KVS_IP_FAMILY_TYPE_NOT_SET) { + pIceServer->ipAddresses.ipv6Address.port = (UINT16) getInt16((INT16) port); + CHK_STATUS(getIpAddrStr(&pIceServer->ipAddresses.ipv6Address, addressResolvedIPv6, ARRAY_SIZE(addressResolvedIPv6))); + DLOGD("Resolved ICE Server IPv6 address for %s: %s with port: %u", pIceServer->url, addressResolvedIPv6, + pIceServer->ipAddresses.ipv6Address.port); + } CleanUp: diff --git a/src/source/Ice/IceUtils.h b/src/source/Ice/IceUtils.h index d6c57013f6..615cfbdeaa 100644 --- a/src/source/Ice/IceUtils.h +++ b/src/source/Ice/IceUtils.h @@ -55,7 +55,7 @@ typedef struct { BOOL isTurn; BOOL isSecure; CHAR url[MAX_ICE_CONFIG_URI_BUFFER_LEN]; - KvsIpAddress ipAddress; + DualKvsIpAddresses ipAddresses; CHAR username[MAX_ICE_CONFIG_USER_NAME_BUFFER_LEN]; CHAR credential[MAX_ICE_CONFIG_CREDENTIAL_BUFFER_LEN]; KVS_SOCKET_PROTOCOL transport; diff --git a/src/source/Ice/NatBehaviorDiscovery.c b/src/source/Ice/NatBehaviorDiscovery.c index 68cf167505..28b280e7d4 100644 --- a/src/source/Ice/NatBehaviorDiscovery.c +++ b/src/source/Ice/NatBehaviorDiscovery.c @@ -123,7 +123,8 @@ STATUS discoverNatMappingBehavior(PIceServer pStunServer, PNatTestData data, PSo /* execute test I */ DLOGD("Running mapping behavior test I. Send binding request"); - CHK_STATUS(executeNatTest(bindingRequest, &pStunServer->ipAddress, pSocketConnection, testIndex++, data, &bindingResponse)); + CHK_STATUS(executeNatTest(bindingRequest, &pStunServer->ipAddresses.ipv4Address, pSocketConnection, testIndex++, data, &bindingResponse)); + // TODO: Nat behavior discovery ipv6? if (bindingResponse == NULL) { natMappingBehavior = NAT_BEHAVIOR_NO_UDP_CONNECTIVITY; @@ -144,7 +145,7 @@ STATUS discoverNatMappingBehavior(PIceServer pStunServer, PNatTestData data, PSo /* execute test II */ DLOGD("Running mapping behavior test II. Send binding request to alternate address but primary port"); testDestAddress = otherAddress; - testDestAddress.port = pStunServer->ipAddress.port; + testDestAddress.port = pStunServer->ipAddresses.ipv4Address.port; CHK_STATUS(executeNatTest(bindingRequest, &testDestAddress, pSocketConnection, testIndex++, data, &bindingResponse)); CHK_ERR(bindingResponse != NULL, retStatus, "Expect to receive binding response"); @@ -208,7 +209,7 @@ STATUS discoverNatFilteringBehavior(PIceServer pStunServer, PNatTestData data, P /* execute test I */ DLOGD("Running filtering behavior test I. Send binding request"); - CHK_STATUS(executeNatTest(bindingRequest, &pStunServer->ipAddress, pSocketConnection, testIndex++, data, &bindingResponse)); + CHK_STATUS(executeNatTest(bindingRequest, &pStunServer->ipAddresses.ipv4Address, pSocketConnection, testIndex++, data, &bindingResponse)); if (bindingResponse == NULL) { natFilteringBehavior = NAT_BEHAVIOR_NO_UDP_CONNECTIVITY; CHK(FALSE, retStatus); @@ -219,7 +220,7 @@ STATUS discoverNatFilteringBehavior(PIceServer pStunServer, PNatTestData data, P CHK_STATUS(appendStunChangeRequestAttribute(bindingRequest, STUN_ATTRIBUTE_CHANGE_REQUEST_FLAG_CHANGE_IP | STUN_ATTRIBUTE_CHANGE_REQUEST_FLAG_CHANGE_PORT)); - CHK_STATUS(executeNatTest(bindingRequest, &pStunServer->ipAddress, pSocketConnection, testIndex++, data, &bindingResponse)); + CHK_STATUS(executeNatTest(bindingRequest, &pStunServer->ipAddresses.ipv4Address, pSocketConnection, testIndex++, data, &bindingResponse)); if (bindingResponse != NULL) { natFilteringBehavior = NAT_BEHAVIOR_ENDPOINT_INDEPENDENT; CHK(FALSE, retStatus); @@ -230,7 +231,7 @@ STATUS discoverNatFilteringBehavior(PIceServer pStunServer, PNatTestData data, P CHK_STATUS(getStunAttribute(bindingRequest, STUN_ATTRIBUTE_TYPE_CHANGE_REQUEST, (PStunAttributeHeader*) &pStunAttributeChangeRequest)); pStunAttributeChangeRequest->changeFlag = STUN_ATTRIBUTE_CHANGE_REQUEST_FLAG_CHANGE_PORT; - CHK_STATUS(executeNatTest(bindingRequest, &pStunServer->ipAddress, pSocketConnection, testIndex++, data, &bindingResponse)); + CHK_STATUS(executeNatTest(bindingRequest, &pStunServer->ipAddresses.ipv4Address, pSocketConnection, testIndex++, data, &bindingResponse)); if (bindingResponse != NULL) { natFilteringBehavior = NAT_BEHAVIOR_ADDRESS_DEPENDENT; @@ -291,15 +292,15 @@ STATUS discoverNatBehavior(PCHAR stunServer, NAT_BEHAVIOR* pNatMappingBehavior, /* use the first usable local interface to create socket */ for (i = 0; i < localNetworkInterfaceCount; ++i) { - if (localNetworkInterfaces[i].family == iceServerStun.ipAddress.family) { + if (localNetworkInterfaces[i].family == iceServerStun.ipAddresses.ipv4Address.family) { pSelectedLocalInterface = &localNetworkInterfaces[i]; break; } } CHK_WARN(pSelectedLocalInterface != NULL, retStatus, "No usable local interface"); - CHK_STATUS(createSocketConnection(iceServerStun.ipAddress.family, KVS_SOCKET_PROTOCOL_UDP, pSelectedLocalInterface, NULL, (UINT64) &customData, - natTestIncomingDataHandler, 0, &pSocketConnection)); + CHK_STATUS(createSocketConnection(iceServerStun.ipAddresses.ipv4Address.family, KVS_SOCKET_PROTOCOL_UDP, pSelectedLocalInterface, NULL, + (UINT64) &customData, natTestIncomingDataHandler, 0, &pSocketConnection)); ATOMIC_STORE_BOOL(&pSocketConnection->receiveData, TRUE); CHK_STATUS(createConnectionListener(&pConnectionListener)); @@ -322,8 +323,8 @@ STATUS discoverNatBehavior(PCHAR stunServer, NAT_BEHAVIOR* pNatMappingBehavior, CHK_STATUS(connectionListenerRemoveAllConnection(pConnectionListener)); freeSocketConnection(&pSocketConnection); - CHK_STATUS(createSocketConnection(iceServerStun.ipAddress.family, KVS_SOCKET_PROTOCOL_UDP, pSelectedLocalInterface, NULL, (UINT64) &customData, - natTestIncomingDataHandler, 0, &pSocketConnection)); + CHK_STATUS(createSocketConnection(iceServerStun.ipAddresses.ipv4Address.family, KVS_SOCKET_PROTOCOL_UDP, pSelectedLocalInterface, NULL, + (UINT64) &customData, natTestIncomingDataHandler, 0, &pSocketConnection)); ATOMIC_STORE_BOOL(&pSocketConnection->receiveData, TRUE); CHK_STATUS(connectionListenerAddConnection(pConnectionListener, pSocketConnection)); diff --git a/src/source/Ice/Network.c b/src/source/Ice/Network.c index 8a46bf14ff..d1b9711ea8 100644 --- a/src/source/Ice/Network.c +++ b/src/source/Ice/Network.c @@ -38,7 +38,7 @@ STATUS getLocalhostIpAddresses(PKvsIpAddress destIpList, PUINT32 pDestIpListLen, // Skip inactive interfaces and loop back interfaces if (aa->OperStatus == IfOperStatusUp && aa->IfType != IF_TYPE_SOFTWARE_LOOPBACK) { char ifa_name[BUFSIZ]; - memset(ifa_name, 0, BUFSIZ); + MEMSET(ifa_name, 0, BUFSIZ); WideCharToMultiByte(CP_ACP, 0, aa->FriendlyName, wcslen(aa->FriendlyName), ifa_name, BUFSIZ, NULL, NULL); for (ua = aa->FirstUnicastAddress; ua != NULL; ua = ua->Next) { @@ -336,17 +336,32 @@ STATUS socketWrite(INT32 sockfd, const void* pBuffer, SIZE_T length) BOOL isIpAddr(PCHAR hostname, UINT16 length) { - BOOL status = TRUE; + UINT32 offset = 0; UINT32 ip_1, ip_2, ip_3, ip_4, ip_5, ip_6, ip_7, ip_8; - if (hostname == NULL || length > MAX_ICE_CONFIG_URI_LEN) { - DLOGW("Provided NULL hostname"); - status = FALSE; - } else { - status = (SSCANF(hostname, IPV4_TEMPLATE, &ip_1, &ip_2, &ip_3, &ip_4) == 4 && ip_1 >= 0 && ip_1 <= 255 && ip_2 >= 0 && ip_2 <= 255 && - ip_3 >= 0 && ip_3 <= 255 && ip_4 >= 0 && ip_4 <= 255) || - (SSCANF(hostname, IPV6_TEMPLATE, &ip_1, &ip_2, &ip_3, &ip_4, &ip_5, &ip_6, &ip_7, &ip_8) == 8); + + if (hostname == NULL) { + DLOGW("Provided NULL hostname."); + return FALSE; + } + if (length >= MAX_ICE_CONFIG_URI_LEN) { + DLOGW("Provided invalid hostname length: %u.", length); + return FALSE; + } + + // Check if IPv4 address. + if (SSCANF(hostname, "%u.%u.%u.%u%n", &ip_1, &ip_2, &ip_3, &ip_4, &offset) == 4 && ip_1 <= 255 && ip_2 <= 255 && ip_3 <= 255 && ip_4 <= 255 && + offset == STRLEN(hostname)) { + return TRUE; + } + + // Check if IPv6 address. + offset = 0; + if (SSCANF(hostname, "%x:%x:%x:%x:%x:%x:%x:%x%n", &ip_1, &ip_2, &ip_3, &ip_4, &ip_5, &ip_6, &ip_7, &ip_8, &offset) == 8 && + offset == STRLEN(hostname)) { + return TRUE; } - return status; + + return FALSE; } STATUS getIpAddrFromDnsHostname(PCHAR hostname, PCHAR address, UINT16 lengthSrc, UINT16 maxLenDst) @@ -361,7 +376,6 @@ STATUS getIpAddrFromDnsHostname(PCHAR hostname, PCHAR address, UINT16 lengthSrc, // https://docs.aws.amazon.com/vpc/latest/userguide/vpc-dns.html#vpc-dns-hostnames // So, we first try to parse the IP from the hostname if it conforms to this format // For example: 35-90-63-38.t-ae7dd61a.kinesisvideo.us-west-2.amazonaws.com - // Note: public IPv6 DNS hostnames are not available while (hostNameLen > 0 && hostname[i] != '.') { if (hostname[i] >= '0' && hostname[i] <= '9') { if (j > maxLenDst) { @@ -392,20 +406,88 @@ STATUS getIpAddrFromDnsHostname(PCHAR hostname, PCHAR address, UINT16 lengthSrc, return retStatus; } -STATUS getIpWithHostName(PCHAR hostname, PKvsIpAddress destIp) +// NOTE: IPv6 address parsing assumes the full notation is used without any compression (e.g., no '::' usage). +STATUS getDualStackIpAddrFromDnsHostname(PCHAR hostname, PCHAR ipv4Address, PCHAR ipv6Address, UINT16 lengthSrc, UINT16 maxLenV4Dst, + UINT16 maxLenV6Dst) +{ + STATUS retStatus = STATUS_SUCCESS; + UINT8 i = 0, j = 0; + UINT16 hostNameLen = lengthSrc; + CHK(hostname != NULL && ipv4Address != NULL && ipv6Address != NULL, STATUS_NULL_ARG); + CHK(hostNameLen > 0 && hostNameLen < MAX_ICE_CONFIG_URI_LEN, STATUS_INVALID_ARG); + CHAR c; + + // Dual-stack TURN server URLs conform with the following IPv4 and IPv6 DNS hostname format: + // 35-90-63-38_2001-0db8-85a3-0000-0000-8a2e-0370-7334.t-ae7dd61a.kinesisvideo.us-west-2.api.aws + + // Parse the IPv4 portion. + while (hostNameLen > 0 && hostname[i] != '_') { + CHK_WARN(j < maxLenV4Dst, STATUS_INVALID_ADDRESS_LENGTH, "Generated IPv4 address is past allowed size."); + + c = hostname[i]; + + if (c >= '0' && c <= '9') { + ipv4Address[j] = hostname[i]; + } else if (hostname[i] == '-') { + ipv4Address[j] = '.'; + } else { + CHK_WARN(FALSE, STATUS_INVALID_ARG, "Parsing IPv4 address failed, received unexpected hostname format: %s", hostname); + } + + j++; + i++; + hostNameLen--; + } + ipv4Address[j] = '\0'; + + j = 0; + i++; + hostNameLen--; + + // Parse the IPv6 portion. + while (hostNameLen > 0 && hostname[i] != '.') { + CHK_WARN(j < maxLenV6Dst, STATUS_INVALID_ADDRESS_LENGTH, "Generated IPv6 address is past allowed size."); + + c = hostname[i]; + + if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')) { + ipv6Address[j] = hostname[i]; + } else if (hostname[i] == '-') { + ipv6Address[j] = ':'; + } else { + CHK_WARN(FALSE, STATUS_INVALID_ARG, "Parsing IPv6 address failed, received unexpected hostname format: %s", hostname); + } + + j++; + i++; + hostNameLen--; + } + ipv6Address[j] = '\0'; + +CleanUp: + return retStatus; +} + +STATUS getIpWithHostName(PCHAR hostname, PDualKvsIpAddresses destIps) { STATUS retStatus = STATUS_SUCCESS; INT32 errCode; UINT16 hostnameLen, addrLen; PCHAR errStr; struct addrinfo *res, *rp; - BOOL resolved = FALSE; - struct sockaddr_in* ipv4Addr; - struct sockaddr_in6* ipv6Addr; + BOOL ipv4Resolved = FALSE; + BOOL ipv6Resolved = FALSE; + struct sockaddr_in* ipv4SockAddr; + struct sockaddr_in6* ipv6SockAddr; struct in_addr inaddr; + struct in6_addr in6addr; - CHAR addr[KVS_IP_ADDRESS_STRING_BUFFER_LEN + 1] = {'\0'}; + CHAR addr[KVS_IP_ADDRESS_STRING_BUFFER_LEN] = {'\0'}; + CHAR ipv4Addr[KVS_IP_ADDRESS_STRING_BUFFER_LEN] = {'\0'}; + CHAR ipv6Addr[KVS_IP_ADDRESS_STRING_BUFFER_LEN] = {'\0'}; BOOL isStunServer; + BOOL wasAddressParseSuccessful = FALSE; + BOOL dualStackEnvVarSet = FALSE; CHK(hostname != NULL, STATUS_NULL_ARG); DLOGI("ICE SERVER Hostname received: %s", hostname); @@ -413,21 +495,46 @@ STATUS getIpWithHostName(PCHAR hostname, PKvsIpAddress destIp) hostnameLen = STRLEN(hostname); addrLen = SIZEOF(addr); isStunServer = STRNCMP(hostname, KINESIS_VIDEO_STUN_URL_PREFIX, KINESIS_VIDEO_STUN_URL_PREFIX_LENGTH) == 0; + dualStackEnvVarSet = GETENV(USE_DUAL_STACK_ENDPOINTS_ENV_VAR) != NULL; // Adding this check in case we directly get an IP address. With the current usage pattern, // there is no way this function would receive an address directly, but having this check // in place anyways if (isIpAddr(hostname, hostnameLen)) { MEMCPY(addr, hostname, hostnameLen); + addr[hostnameLen] = '\0'; } else if (!isStunServer) { - retStatus = getIpAddrFromDnsHostname(hostname, addr, hostnameLen, addrLen); + // Try to parse the address from the TURN server hostname. + + if (dualStackEnvVarSet) { + DLOGD("Attempting to parse dual-stack IP addresses from TURN server hostname: %s", hostname); + + retStatus = getDualStackIpAddrFromDnsHostname(hostname, ipv4Addr, ipv6Addr, hostnameLen, ARRAY_SIZE(ipv4Addr), ARRAY_SIZE(ipv6Addr)); + if (retStatus == STATUS_SUCCESS) { + DLOGD("Parsed dual-stack IP addresses from TURN server hostname: IPv4 %s, IPv6 %s", ipv4Addr, ipv6Addr); + } + } else { + DLOGD("Attempting to parse IP address from legacy TURN server hostname: %s", hostname); + + retStatus = getIpAddrFromDnsHostname(hostname, addr, hostnameLen, addrLen); + if (retStatus == STATUS_SUCCESS) { + DLOGD("Parsed IP address from legacy TURN server hostname: %s", addr); + } + } } - // Verify the generated address has the format x.x.x.x - if (!isIpAddr(addr, hostnameLen) || retStatus != STATUS_SUCCESS) { - // Only print the message for TURN servers since STUN addresses don't have the IP in the URL + wasAddressParseSuccessful = isIpAddr(addr, STRLEN(addr)) || (isIpAddr(ipv4Addr, STRLEN(ipv4Addr)) && isIpAddr(ipv6Addr, STRLEN(ipv6Addr))); + + // Fall back to getaddrinfo if parsing the address from the hostname was not possible or failed. + if (!wasAddressParseSuccessful || retStatus != STATUS_SUCCESS) { + // Only print the warning for TURN servers since STUN addresses don't get parsed from the hostname. if (!isStunServer) { - DLOGW("Parsing for address failed for %s, fallback to getaddrinfo", hostname); + DLOGW("Parsing for TURN address failed for %s, fallback to getaddrinfo", hostname); + } + + // Skip the IPv6 resolution if dual stack env var is not set. + if (!dualStackEnvVarSet) { + ipv6Resolved = TRUE; } errCode = getaddrinfo(hostname, NULL, NULL, &res); @@ -435,27 +542,69 @@ STATUS getIpWithHostName(PCHAR hostname, PKvsIpAddress destIp) errStr = errCode == EAI_SYSTEM ? (strerror(errno)) : ((PCHAR) gai_strerror(errCode)); CHK_ERR(FALSE, STATUS_RESOLVE_HOSTNAME_FAILED, "getaddrinfo() with errno %s", errStr); } - for (rp = res; rp != NULL && !resolved; rp = rp->ai_next) { - if (rp->ai_family == AF_INET) { - ipv4Addr = (struct sockaddr_in*) rp->ai_addr; - destIp->family = KVS_IP_FAMILY_TYPE_IPV4; - MEMCPY(destIp->address, &ipv4Addr->sin_addr, IPV4_ADDRESS_LENGTH); - resolved = TRUE; - } else if (rp->ai_family == AF_INET6) { - ipv6Addr = (struct sockaddr_in6*) rp->ai_addr; - destIp->family = KVS_IP_FAMILY_TYPE_IPV6; - MEMCPY(destIp->address, &ipv6Addr->sin6_addr, IPV6_ADDRESS_LENGTH); - resolved = TRUE; + for (rp = res; rp != NULL && !(ipv4Resolved && ipv6Resolved); rp = rp->ai_next) { + if (rp->ai_family == AF_INET && !ipv4Resolved) { + ipv4SockAddr = (struct sockaddr_in*) rp->ai_addr; + destIps->ipv4Address.family = KVS_IP_FAMILY_TYPE_IPV4; + MEMCPY(destIps->ipv4Address.address, &ipv4SockAddr->sin_addr, IPV4_ADDRESS_LENGTH); + + CHK_STATUS(getIpAddrStr(&(destIps->ipv4Address), ipv4Addr, ARRAY_SIZE(ipv4Addr))); + DLOGD("Found an IPv4 ICE server addresss: %s", ipv4Addr); + + ipv4Resolved = TRUE; + + } else if (rp->ai_family == AF_INET6 && !ipv6Resolved) { + ipv6SockAddr = (struct sockaddr_in6*) rp->ai_addr; + destIps->ipv6Address.family = KVS_IP_FAMILY_TYPE_IPV6; + MEMCPY(destIps->ipv6Address.address, &ipv6SockAddr->sin6_addr, IPV6_ADDRESS_LENGTH); + + CHK_STATUS(getIpAddrStr(&(destIps->ipv6Address), ipv6Addr, ARRAY_SIZE(ipv6Addr))); + DLOGD("Found an IPv6 ICE server addresss: %s", ipv6Addr); + + ipv6Resolved = TRUE; } } freeaddrinfo(res); - CHK_ERR(resolved, STATUS_HOSTNAME_NOT_FOUND, "Could not find network address of %s", hostname); - } + CHK_ERR(ipv4Resolved || ipv6Resolved, STATUS_HOSTNAME_NOT_FOUND, "Could not find network address of %s", hostname); + } else { + // Address parsing was successful. Capture the address(es). + + // Both addresses should be present in TURN url for dual-stack case. + if (STRLEN(ipv4Addr) > 0 && STRLEN(ipv6Addr) > 0) { + // Dual-stack case. + + if (inet_pton(AF_INET, ipv4Addr, &inaddr) == 1) { + destIps->ipv4Address.family = KVS_IP_FAMILY_TYPE_IPV4; + MEMCPY(destIps->ipv4Address.address, &inaddr, IPV4_ADDRESS_LENGTH); + } else { + DLOGW("inet_pton failed on IPv4 ICE server address: %s", ipv4Addr); + retStatus = STATUS_INVALID_ARG; + } + + if (inet_pton(AF_INET6, ipv6Addr, &in6addr) == 1) { + destIps->ipv6Address.family = KVS_IP_FAMILY_TYPE_IPV6; + MEMCPY(destIps->ipv6Address.address, &in6addr, IPV6_ADDRESS_LENGTH); + } else { + DLOGW("inet_pton failed on IPv6 ICE server address: %s", ipv6Addr); + retStatus = STATUS_INVALID_ARG; + } - else { - inet_pton(AF_INET, addr, &inaddr); - destIp->family = KVS_IP_FAMILY_TYPE_IPV4; - MEMCPY(destIp->address, &inaddr, IPV4_ADDRESS_LENGTH); + } else { + // Legacy case. + + if (inet_pton(AF_INET, addr, &inaddr) == 1) { + destIps->ipv4Address.family = KVS_IP_FAMILY_TYPE_IPV4; + MEMCPY(destIps->ipv4Address.address, &inaddr, IPV4_ADDRESS_LENGTH); + } else if (inet_pton(AF_INET6, addr, &in6addr) == 1) { + // This case will never happen with current TURN server URL format, + // but adding in case a hardcoded direct IPv6 address gets used. + destIps->ipv6Address.family = KVS_IP_FAMILY_TYPE_IPV6; + MEMCPY(destIps->ipv6Address.address, &in6addr, IPV6_ADDRESS_LENGTH); + } else { + DLOGW("inet_pton failed on legacy ICE server address: %s", addr); + retStatus = STATUS_INVALID_ARG; + } + } } CleanUp: diff --git a/src/source/Ice/Network.h b/src/source/Ice/Network.h index ca5b6e0aad..52aeb17bcf 100644 --- a/src/source/Ice/Network.h +++ b/src/source/Ice/Network.h @@ -121,7 +121,7 @@ STATUS socketWrite(INT32, const void*, SIZE_T); * * @return - STATUS status of execution */ -STATUS getIpWithHostName(PCHAR, PKvsIpAddress); +STATUS getIpWithHostName(PCHAR, PDualKvsIpAddresses); /** * @param - PCHAR - IN - IP address string to verify if it is IPv4 or IPv6 format diff --git a/src/source/Ice/TurnConnection.c b/src/source/Ice/TurnConnection.c index e3e799a616..6113d99a5f 100644 --- a/src/source/Ice/TurnConnection.c +++ b/src/source/Ice/TurnConnection.c @@ -55,7 +55,7 @@ STATUS getHostnameFromUrl(PCHAR url, PCHAR* ppHostname) STATUS createTurnConnection(PIceServer pTurnServer, TIMER_QUEUE_HANDLE timerQueueHandle, TURN_CONNECTION_DATA_TRANSFER_MODE dataTransferMode, KVS_SOCKET_PROTOCOL protocol, PTurnConnectionCallbacks pTurnConnectionCallbacks, PSocketConnection pTurnSocket, - PConnectionListener pConnectionListener, PTurnConnection* ppTurnConnection) + PConnectionListener pConnectionListener, KVS_IP_FAMILY_TYPE turnServerIpFamily, PTurnConnection* ppTurnConnection) { UNUSED_PARAM(dataTransferMode); ENTERS(); @@ -112,6 +112,7 @@ STATUS createTurnConnection(PIceServer pTurnServer, TIMER_QUEUE_HANDLE timerQueu pTurnConnection->allocationExpirationTime = INVALID_TIMESTAMP_VALUE; pTurnConnection->nextAllocationRefreshTime = 0; pTurnConnection->currentTimerCallingPeriod = DEFAULT_TURN_TIMER_INTERVAL_BEFORE_READY; + pTurnConnection->ipFamilyType = turnServerIpFamily; SNPRINTF(turnStateMachineName, MAX_STATE_MACHINE_NAME_LENGTH, "%s-%p", TURN_STATE_MACHINE_NAME, (PVOID) pTurnConnection); CHK_STATUS(createStateMachineWithName(TURN_CONNECTION_STATE_MACHINE_STATES, TURN_CONNECTION_STATE_MACHINE_STATE_COUNT, (UINT64) pTurnConnection, @@ -730,8 +731,12 @@ STATUS turnConnectionAddPeer(PTurnConnection pTurnConnection, PKvsIpAddress pPee BOOL locked = FALSE; CHK(pTurnConnection != NULL && pPeerAddress != NULL, STATUS_NULL_ARG); - CHK(pTurnConnection->turnServer.ipAddress.family == pPeerAddress->family, STATUS_INVALID_ARG); - CHK_WARN(IS_IPV4_ADDR(pPeerAddress), retStatus, "Drop IPv6 turn peer because only IPv4 turn peer is supported right now"); + + if (pTurnConnection->ipFamilyType == KVS_IP_FAMILY_TYPE_IPV4) { + CHK_WARN(IS_IPV4_ADDR(pPeerAddress), STATUS_INVALID_ARG, "Dropping IPv6 peer for IPv4 TURN connection."); + } else if (pTurnConnection->ipFamilyType == KVS_IP_FAMILY_TYPE_IPV6) { + CHK_WARN(IS_IPV6_ADDR(pPeerAddress), STATUS_INVALID_ARG, "Dropping IPv4 peer for IPv6 TURN connection."); + } MUTEX_LOCK(pTurnConnection->lock); locked = TRUE; @@ -754,7 +759,7 @@ STATUS turnConnectionAddPeer(PTurnConnection pTurnConnection, PKvsIpAddress pPee pTurnPeer->firstTimeCreatePermResponse = TRUE; pTurnPeer->firstTimeBindChannelResponse = TRUE; - CHK_STATUS(xorIpAddress(&pTurnPeer->xorAddress, NULL)); /* only work for IPv4 for now */ + // CHK_STATUS(xorIpAddress(&pTurnPeer->xorAddress, NULL)); /* only work for IPv4 for now */ CHK_STATUS(createTransactionIdStore(DEFAULT_MAX_STORED_TRANSACTION_ID_COUNT, &pTurnPeer->pTransactionIdStore)); pTurnPeer = NULL; @@ -781,6 +786,7 @@ STATUS turnConnectionSendData(PTurnConnection pTurnConnection, PBYTE pBuf, UINT3 CHAR ipAddrStr[KVS_IP_ADDRESS_STRING_BUFFER_LEN]; BOOL locked = FALSE; BOOL sendLocked = FALSE; + PKvsIpAddress pTurnServerIp = NULL; CHK(pTurnConnection != NULL && pDestIp != NULL, STATUS_NULL_ARG); CHK(pBuf != NULL && bufLen > 0, STATUS_INVALID_ARG); @@ -826,8 +832,9 @@ STATUS turnConnectionSendData(PTurnConnection pTurnConnection, PBYTE pBuf, UINT3 putInt16((PINT16) (pTurnConnection->sendDataBuffer + 2), (UINT16) bufLen); MEMCPY(pTurnConnection->sendDataBuffer + TURN_DATA_CHANNEL_SEND_OVERHEAD, pBuf, bufLen); - retStatus = iceUtilsSendData(pTurnConnection->sendDataBuffer, paddedDataLen, &pTurnConnection->turnServer.ipAddress, - pTurnConnection->pControlChannel, NULL, FALSE); + getTurnConnectionIpAddress(pTurnConnection, &pTurnServerIp); + + retStatus = iceUtilsSendData(pTurnConnection->sendDataBuffer, paddedDataLen, pTurnServerIp, pTurnConnection->pControlChannel, NULL, FALSE); if (STATUS_FAILED(retStatus)) { DLOGW("iceUtilsSendData failed with 0x%08x", retStatus); @@ -895,6 +902,7 @@ STATUS turnConnectionRefreshAllocation(PTurnConnection pTurnConnection) STATUS retStatus = STATUS_SUCCESS; UINT64 currTime = 0; PStunAttributeLifetime pStunAttributeLifetime = NULL; + PKvsIpAddress pTurnServerIp = NULL; CHK(pTurnConnection != NULL, STATUS_NULL_ARG); @@ -913,9 +921,10 @@ STATUS turnConnectionRefreshAllocation(PTurnConnection pTurnConnection) pStunAttributeLifetime->lifetime = DEFAULT_TURN_ALLOCATION_LIFETIME_SECONDS; + getTurnConnectionIpAddress(pTurnConnection, &pTurnServerIp); + CHK_STATUS(iceUtilsSendStunPacket(pTurnConnection->pTurnAllocationRefreshPacket, pTurnConnection->longTermKey, - ARRAY_SIZE(pTurnConnection->longTermKey), &pTurnConnection->turnServer.ipAddress, - pTurnConnection->pControlChannel, NULL, FALSE)); + ARRAY_SIZE(pTurnConnection->longTermKey), pTurnServerIp, pTurnConnection->pControlChannel, NULL, FALSE)); pTurnConnection->nextAllocationRefreshTime = currTime + DEFAULT_TURN_SEND_REFRESH_INVERVAL; @@ -1087,6 +1096,7 @@ STATUS checkTurnPeerConnections(PTurnConnection pTurnConnection) PStunAttributeAddress pStunAttributeAddress = NULL; PStunAttributeChannelNumber pStunAttributeChannelNumber = NULL; UINT32 i = 0; + PKvsIpAddress pTurnServerIp = NULL; UNUSED_PARAM(sendStatus); @@ -1111,9 +1121,10 @@ STATUS checkTurnPeerConnections(PTurnConnection pTurnConnection) CHK(pTurnPeer->pTransactionIdStore != NULL, STATUS_INVALID_OPERATION); transactionIdStoreInsert(pTurnPeer->pTransactionIdStore, pTurnConnection->pTurnCreatePermissionPacket->header.transactionId); - sendStatus = iceUtilsSendStunPacket(pTurnConnection->pTurnCreatePermissionPacket, pTurnConnection->longTermKey, - ARRAY_SIZE(pTurnConnection->longTermKey), &pTurnConnection->turnServer.ipAddress, - pTurnConnection->pControlChannel, NULL, FALSE); + getTurnConnectionIpAddress(pTurnConnection, &pTurnServerIp); + sendStatus = + iceUtilsSendStunPacket(pTurnConnection->pTurnCreatePermissionPacket, pTurnConnection->longTermKey, + ARRAY_SIZE(pTurnConnection->longTermKey), pTurnServerIp, pTurnConnection->pControlChannel, NULL, FALSE); } else if (pTurnPeer->connectionState == TURN_PEER_CONN_STATE_BIND_CHANNEL) { if (pTurnPeer->firstTimeBindChannelReq) { @@ -1137,9 +1148,10 @@ STATUS checkTurnPeerConnections(PTurnConnection pTurnConnection) CHK(pTurnPeer->pTransactionIdStore != NULL, STATUS_INVALID_OPERATION); transactionIdStoreInsert(pTurnPeer->pTransactionIdStore, pTurnConnection->pTurnChannelBindPacket->header.transactionId); - sendStatus = iceUtilsSendStunPacket(pTurnConnection->pTurnChannelBindPacket, pTurnConnection->longTermKey, - ARRAY_SIZE(pTurnConnection->longTermKey), &pTurnConnection->turnServer.ipAddress, - pTurnConnection->pControlChannel, NULL, FALSE); + getTurnConnectionIpAddress(pTurnConnection, &pTurnServerIp); + sendStatus = + iceUtilsSendStunPacket(pTurnConnection->pTurnChannelBindPacket, pTurnConnection->longTermKey, + ARRAY_SIZE(pTurnConnection->longTermKey), pTurnServerIp, pTurnConnection->pControlChannel, NULL, FALSE); } } @@ -1209,7 +1221,7 @@ STATUS turnConnectionGetLongTermKey(PCHAR username, PCHAR realm, PCHAR password, } STATUS turnConnectionPackageTurnAllocationRequest(PCHAR username, PCHAR realm, PBYTE nonce, UINT16 nonceLen, UINT32 lifetime, - PStunPacket* ppStunPacket) + PStunPacket* ppStunPacket, KVS_IP_FAMILY_TYPE turnConnectionFamilyType) { STATUS retStatus = STATUS_SUCCESS; PStunPacket pTurnAllocateRequest = NULL; @@ -1227,6 +1239,11 @@ STATUS turnConnectionPackageTurnAllocationRequest(PCHAR username, PCHAR realm, P CHK_STATUS(appendStunUsernameAttribute(pTurnAllocateRequest, username)); CHK_STATUS(appendStunRealmAttribute(pTurnAllocateRequest, realm)); CHK_STATUS(appendStunNonceAttribute(pTurnAllocateRequest, nonce, nonceLen)); + + // KVS TURN server will default to IPv4 if no address family attribute is specified. + if (turnConnectionFamilyType == KVS_IP_FAMILY_TYPE_IPV6) { + CHK_STATUS(appendStunAllocationAddressFamily(pTurnAllocateRequest, KVS_IP_FAMILY_TYPE_IPV6)); + } } CleanUp: @@ -1281,3 +1298,22 @@ VOID turnConnectionFatalError(PTurnConnection pTurnConnection, STATUS errorStatu pTurnConnection->errorStatus = errorStatus; pTurnConnection->state = TURN_STATE_FAILED; } + +STATUS getTurnConnectionIpAddress(PTurnConnection pTurnConnection, PKvsIpAddress* ppTurnConnectionIpAddress) +{ + ENTERS(); + STATUS retStatus = STATUS_SUCCESS; + + CHK_ERR(ppTurnConnectionIpAddress != NULL, STATUS_NULL_ARG, "ppTurnConnectionIp is NULL"); + CHK_ERR(pTurnConnection->ipFamilyType != KVS_IP_FAMILY_TYPE_NOT_SET, STATUS_INVALID_ARG, "pTurnConnection ip family type is not set"); + + if (pTurnConnection->ipFamilyType == KVS_IP_FAMILY_TYPE_IPV4) { + *ppTurnConnectionIpAddress = &pTurnConnection->turnServer.ipAddresses.ipv4Address; + } else { + *ppTurnConnectionIpAddress = &pTurnConnection->turnServer.ipAddresses.ipv6Address; + } +CleanUp: + CHK_LOG_ERR(retStatus); + LEAVES(); + return retStatus; +} diff --git a/src/source/Ice/TurnConnection.h b/src/source/Ice/TurnConnection.h index 6d73d8b336..dc3ba1bcdc 100644 --- a/src/source/Ice/TurnConnection.h +++ b/src/source/Ice/TurnConnection.h @@ -183,11 +183,13 @@ struct __TurnConnection { BOOL deallocatePacketSent; TurnProfileDiagnostics turnProfileDiagnostics; PStateMachine pStateMachine; + + KVS_IP_FAMILY_TYPE ipFamilyType; }; typedef struct __TurnConnection* PTurnConnection; STATUS createTurnConnection(PIceServer, TIMER_QUEUE_HANDLE, TURN_CONNECTION_DATA_TRANSFER_MODE, KVS_SOCKET_PROTOCOL, PTurnConnectionCallbacks, - PSocketConnection, PConnectionListener, PTurnConnection*); + PSocketConnection, PConnectionListener, KVS_IP_FAMILY_TYPE, PTurnConnection*); STATUS freeTurnConnection(PTurnConnection*); STATUS turnConnectionAddPeer(PTurnConnection, PKvsIpAddress); STATUS turnConnectionSendData(PTurnConnection, PBYTE, UINT32, PKvsIpAddress); @@ -205,7 +207,7 @@ UINT64 turnConnectionGetTime(UINT64); STATUS turnConnectionUpdateNonce(PTurnConnection); STATUS turnConnectionTimerCallback(UINT32, UINT64, UINT64); STATUS turnConnectionGetLongTermKey(PCHAR, PCHAR, PCHAR, PBYTE, UINT32); -STATUS turnConnectionPackageTurnAllocationRequest(PCHAR, PCHAR, PBYTE, UINT16, UINT32, PStunPacket*); +STATUS turnConnectionPackageTurnAllocationRequest(PCHAR, PCHAR, PBYTE, UINT16, UINT32, PStunPacket*, KVS_IP_FAMILY_TYPE); STATUS turnConnectionIncomingDataHandler(PTurnConnection, PBYTE, UINT32, PKvsIpAddress, PKvsIpAddress, PTurnChannelData, PUINT32); @@ -218,6 +220,8 @@ VOID turnConnectionFatalError(PTurnConnection, STATUS); PTurnPeer turnConnectionGetPeerWithChannelNumber(PTurnConnection, UINT16); PTurnPeer turnConnectionGetPeerWithIp(PTurnConnection, PKvsIpAddress); +STATUS getTurnConnectionIpAddress(PTurnConnection, PKvsIpAddress*); + STATUS checkTurnPeerConnections(PTurnConnection); #ifdef __cplusplus diff --git a/src/source/Ice/TurnConnectionStateMachine.c b/src/source/Ice/TurnConnectionStateMachine.c index 6b7ba5775c..bdced3f8a1 100644 --- a/src/source/Ice/TurnConnectionStateMachine.c +++ b/src/source/Ice/TurnConnectionStateMachine.c @@ -206,8 +206,8 @@ STATUS executeCheckSocketConnectionTurnState(UINT64 customData, UINT64 time) if (pTurnConnection->state != TURN_STATE_CHECK_SOCKET_CONNECTION) { pTurnConnection->state = TURN_STATE_CHECK_SOCKET_CONNECTION; - CHK_STATUS( - turnConnectionPackageTurnAllocationRequest(NULL, NULL, NULL, 0, DEFAULT_TURN_ALLOCATION_LIFETIME_SECONDS, &pTurnConnection->pTurnPacket)); + CHK_STATUS(turnConnectionPackageTurnAllocationRequest(NULL, NULL, NULL, 0, DEFAULT_TURN_ALLOCATION_LIFETIME_SECONDS, + &pTurnConnection->pTurnPacket, pTurnConnection->ipFamilyType)); } CleanUp: @@ -259,6 +259,7 @@ STATUS executeGetCredentialsTurnState(UINT64 customData, UINT64 time) STATUS retStatus = STATUS_SUCCESS; PTurnConnection pTurnConnection = (PTurnConnection) customData; UINT64 currentTime; + PKvsIpAddress pTurnServerIp = NULL; CHK(pTurnConnection != NULL, STATUS_NULL_ARG); currentTime = GETTIME(); @@ -278,8 +279,9 @@ STATUS executeGetCredentialsTurnState(UINT64 customData, UINT64 time) } else { CHK(currentTime <= pTurnConnection->stateTimeoutTime, STATUS_TURN_CONNECTION_GET_CREDENTIALS_FAILED); } - CHK_STATUS(iceUtilsSendStunPacket(pTurnConnection->pTurnPacket, NULL, 0, &pTurnConnection->turnServer.ipAddress, pTurnConnection->pControlChannel, - NULL, FALSE)); + + CHK_STATUS(getTurnConnectionIpAddress(pTurnConnection, &pTurnServerIp)); + CHK_STATUS(iceUtilsSendStunPacket(pTurnConnection->pTurnPacket, NULL, 0, pTurnServerIp, pTurnConnection->pControlChannel, NULL, FALSE)); CleanUp: @@ -329,6 +331,8 @@ STATUS executeAllocationTurnState(UINT64 customData, UINT64 time) STATUS retStatus = STATUS_SUCCESS; PTurnConnection pTurnConnection = (PTurnConnection) customData; UINT64 currentTime; + PKvsIpAddress pTurnServerIp = NULL; + CHK(pTurnConnection != NULL, STATUS_NULL_ARG); currentTime = GETTIME(); @@ -340,15 +344,17 @@ STATUS executeAllocationTurnState(UINT64 customData, UINT64 time) CHK_STATUS(turnConnectionGetLongTermKey(pTurnConnection->turnServer.username, pTurnConnection->turnRealm, pTurnConnection->turnServer.credential, pTurnConnection->longTermKey, SIZEOF(pTurnConnection->longTermKey))); - CHK_STATUS(turnConnectionPackageTurnAllocationRequest(pTurnConnection->turnServer.username, pTurnConnection->turnRealm, - pTurnConnection->turnNonce, pTurnConnection->nonceLen, - DEFAULT_TURN_ALLOCATION_LIFETIME_SECONDS, &pTurnConnection->pTurnPacket)); + CHK_STATUS(turnConnectionPackageTurnAllocationRequest( + pTurnConnection->turnServer.username, pTurnConnection->turnRealm, pTurnConnection->turnNonce, pTurnConnection->nonceLen, + DEFAULT_TURN_ALLOCATION_LIFETIME_SECONDS, &pTurnConnection->pTurnPacket, pTurnConnection->ipFamilyType)); pTurnConnection->state = TURN_STATE_ALLOCATION; } else { CHK(currentTime <= pTurnConnection->stateTimeoutTime, STATUS_TURN_CONNECTION_ALLOCATION_FAILED); } + + getTurnConnectionIpAddress(pTurnConnection, &pTurnServerIp); CHK_STATUS(iceUtilsSendStunPacket(pTurnConnection->pTurnPacket, pTurnConnection->longTermKey, ARRAY_SIZE(pTurnConnection->longTermKey), - &pTurnConnection->turnServer.ipAddress, pTurnConnection->pControlChannel, NULL, FALSE)); + pTurnServerIp, pTurnConnection->pControlChannel, NULL, FALSE)); CleanUp: @@ -669,6 +675,7 @@ STATUS executeCleanUpTurnState(UINT64 customData, UINT64 time) STATUS retStatus = STATUS_SUCCESS; PTurnConnection pTurnConnection = (PTurnConnection) customData; PStunAttributeLifetime pStunAttributeLifetime = NULL; + PKvsIpAddress pTurnServerIp = NULL; CHK(pTurnConnection != NULL, STATUS_NULL_ARG); @@ -678,9 +685,9 @@ STATUS executeCleanUpTurnState(UINT64 customData, UINT64 time) (PStunAttributeHeader*) &pStunAttributeLifetime)); CHK(pStunAttributeLifetime != NULL, STATUS_INTERNAL_ERROR); pStunAttributeLifetime->lifetime = 0; + getTurnConnectionIpAddress(pTurnConnection, &pTurnServerIp); CHK_STATUS(iceUtilsSendStunPacket(pTurnConnection->pTurnAllocationRefreshPacket, pTurnConnection->longTermKey, - ARRAY_SIZE(pTurnConnection->longTermKey), &pTurnConnection->turnServer.ipAddress, - pTurnConnection->pControlChannel, NULL, FALSE)); + ARRAY_SIZE(pTurnConnection->longTermKey), pTurnServerIp, pTurnConnection->pControlChannel, NULL, FALSE)); pTurnConnection->deallocatePacketSent = TRUE; } diff --git a/src/source/Include_i.h b/src/source/Include_i.h index a3b897a9a3..a82f4d402b 100644 --- a/src/source/Include_i.h +++ b/src/source/Include_i.h @@ -97,6 +97,7 @@ extern "C" { #define MAX_UDP_PACKET_SIZE 65507 typedef enum { + KVS_IP_FAMILY_TYPE_NOT_SET = (UINT16) 0x0000, // Sentinel value for not yet set IP address. KVS_IP_FAMILY_TYPE_IPV4 = (UINT16) 0x0001, KVS_IP_FAMILY_TYPE_IPV6 = (UINT16) 0x0002, } KVS_IP_FAMILY_TYPE; @@ -108,12 +109,26 @@ typedef struct { BOOL isPointToPoint; } KvsIpAddress, *PKvsIpAddress; -#define IS_IPV4_ADDR(pAddress) ((pAddress)->family == KVS_IP_FAMILY_TYPE_IPV4) +// This structure stores both an IPv4 and IPv6 address (if applicable). +typedef struct { + KvsIpAddress ipv4Address; + KvsIpAddress ipv6Address; +} DualKvsIpAddresses, *PDualKvsIpAddresses; + +static inline BOOL IS_IPV4_ADDR(const KvsIpAddress* pAddress) +{ + return pAddress != NULL && pAddress->family == KVS_IP_FAMILY_TYPE_IPV4; +} + +static inline BOOL IS_IPV6_ADDR(const KvsIpAddress* pAddress) +{ + return pAddress != NULL && pAddress->family == KVS_IP_FAMILY_TYPE_IPV6; +} // Used for ensuring alignment #define ALIGN_UP_TO_MACHINE_WORD(x) ROUND_UP((x), SIZEOF(SIZE_T)) -typedef STATUS (*IceServerSetIpFunc)(UINT64, PCHAR, PKvsIpAddress); +typedef STATUS (*IceServerSetIpFunc)(UINT64, PCHAR, PDualKvsIpAddresses); STATUS getIpAddrStr(PKvsIpAddress pKvsIpAddress, PCHAR pBuffer, UINT32 bufferLen); //////////////////////////////////////////////////// diff --git a/src/source/PeerConnection/PeerConnection.c b/src/source/PeerConnection/PeerConnection.c index 82b374239a..926301651b 100644 --- a/src/source/PeerConnection/PeerConnection.c +++ b/src/source/PeerConnection/PeerConnection.c @@ -806,25 +806,49 @@ STATUS getStunAddr(PStunIpAddrContext pStunIpAddrCtx) STATUS retStatus = STATUS_SUCCESS; struct addrinfo *rp, *res; struct sockaddr_in* ipv4Addr; - BOOL resolved = FALSE; + struct sockaddr_in6* ipv6Addr; + BOOL ipv4Resolved = FALSE; + BOOL ipv6Resolved = FALSE; + + // Initialize IP address families to a sentinel value + // to indicate that they are not set. + pStunIpAddrCtx->kvsIpAddresses.ipv4Address.family = KVS_IP_FAMILY_TYPE_NOT_SET; + pStunIpAddrCtx->kvsIpAddresses.ipv6Address.family = KVS_IP_FAMILY_TYPE_NOT_SET; + pStunIpAddrCtx->kvsIpAddresses.ipv4Address.port = 0; + pStunIpAddrCtx->kvsIpAddresses.ipv6Address.port = 0; + + // Don't attempt to resolve IPv6 address if not in dual-stack mode. + if (GETENV(USE_DUAL_STACK_ENDPOINTS_ENV_VAR) == NULL) { + ipv6Resolved = TRUE; + } + + DLOGD("Resolving STUN server address for hostname: %s", pStunIpAddrCtx->hostname); errCode = getaddrinfo(pStunIpAddrCtx->hostname, NULL, NULL, &res); if (errCode != 0) { DLOGI("Failed to resolve hostname with errcode: %d", errCode); retStatus = STATUS_RESOLVE_HOSTNAME_FAILED; } else { - for (rp = res; rp != NULL && !resolved; rp = rp->ai_next) { - if (rp->ai_family == AF_INET) { + for (rp = res; rp != NULL && !(ipv4Resolved && ipv6Resolved); rp = rp->ai_next) { + if (!ipv4Resolved && rp->ai_family == AF_INET) { + DLOGD("Found an IPv4 STUN addresss for hostname: %s", pStunIpAddrCtx->hostname); ipv4Addr = (struct sockaddr_in*) rp->ai_addr; - pStunIpAddrCtx->kvsIpAddr.family = KVS_IP_FAMILY_TYPE_IPV4; - pStunIpAddrCtx->kvsIpAddr.port = 0; - MEMCPY(pStunIpAddrCtx->kvsIpAddr.address, &ipv4Addr->sin_addr, IPV4_ADDRESS_LENGTH); - resolved = TRUE; + pStunIpAddrCtx->kvsIpAddresses.ipv4Address.family = KVS_IP_FAMILY_TYPE_IPV4; + MEMCPY(pStunIpAddrCtx->kvsIpAddresses.ipv4Address.address, &ipv4Addr->sin_addr, IPV4_ADDRESS_LENGTH); + ipv4Resolved = TRUE; + } else if (!ipv6Resolved && rp->ai_family == AF_INET6) { + DLOGD("Found an IPv6 STUN addresss for hostname: %s", pStunIpAddrCtx->hostname); + ipv6Addr = (struct sockaddr_in6*) rp->ai_addr; + pStunIpAddrCtx->kvsIpAddresses.ipv6Address.family = KVS_IP_FAMILY_TYPE_IPV6; + MEMCPY(pStunIpAddrCtx->kvsIpAddresses.ipv6Address.address, &ipv6Addr->sin6_addr, IPV6_ADDRESS_LENGTH); + ipv6Resolved = TRUE; + } else { + DLOGD("Invalid family STUN addresss for hostname: %s", pStunIpAddrCtx->hostname); } } freeaddrinfo(res); } - if (!resolved) { + if (!(ipv4Resolved || ipv6Resolved)) { retStatus = STATUS_RESOLVE_HOSTNAME_FAILED; } @@ -833,7 +857,7 @@ STATUS getStunAddr(PStunIpAddrContext pStunIpAddrCtx) return retStatus; } -STATUS onSetStunServerIp(UINT64 customData, PCHAR url, PKvsIpAddress pIpAddr) +STATUS onSetStunServerIp(UINT64 customData, PCHAR url, PDualKvsIpAddresses pIpAddresses) { ENTERS(); UNUSED_PARAM(customData); @@ -863,7 +887,10 @@ STATUS onSetStunServerIp(UINT64 customData, PCHAR url, PKvsIpAddress pIpAddr) pWebRtcClientContext->pStunIpAddrCtx->startTime = 0; CHK_ERR(getStunAddr(pWebRtcClientContext->pStunIpAddrCtx) == STATUS_SUCCESS, retStatus, "Failed to resolve after cache expiry"); } - MEMCPY(pIpAddr, &pWebRtcClientContext->pStunIpAddrCtx->kvsIpAddr, SIZEOF(pWebRtcClientContext->pStunIpAddrCtx->kvsIpAddr)); + MEMCPY(pIpAddresses->ipv4Address.address, &pWebRtcClientContext->pStunIpAddrCtx->kvsIpAddresses.ipv4Address, + SIZEOF(pWebRtcClientContext->pStunIpAddrCtx->kvsIpAddresses.ipv4Address)); + MEMCPY(pIpAddresses->ipv6Address.address, &pWebRtcClientContext->pStunIpAddrCtx->kvsIpAddresses.ipv6Address, + SIZEOF(pWebRtcClientContext->pStunIpAddrCtx->kvsIpAddresses.ipv6Address)); } else { DLOGE("Initialization failed"); } @@ -886,7 +913,8 @@ PVOID resolveStunIceServerIp(PVOID args) UNUSED_PARAM(args); PWebRtcClientContext pWebRtcClientContext = getWebRtcClientInstance(); BOOL locked = FALSE; - CHAR addressResolved[KVS_IP_ADDRESS_STRING_BUFFER_LEN + 1] = {'\0'}; + CHAR addressResolvedIPv4[KVS_IP_ADDRESS_STRING_BUFFER_LEN + 1] = {'\0'}; + CHAR addressResolvedIPv6[KVS_IP_ADDRESS_STRING_BUFFER_LEN + 1] = {'\0'}; PCHAR pRegion; PCHAR pHostnamePostfix; UINT64 stunDnsResolutionStartTime = 0; @@ -912,9 +940,21 @@ PVOID resolveStunIceServerIp(PVOID args) KINESIS_VIDEO_STUN_URL_WITHOUT_PORT, pRegion, pHostnamePostfix); stunDnsResolutionStartTime = GETTIME(); if (getStunAddr(pWebRtcClientContext->pStunIpAddrCtx) == STATUS_SUCCESS) { - getIpAddrStr(&pWebRtcClientContext->pStunIpAddrCtx->kvsIpAddr, addressResolved, ARRAY_SIZE(addressResolved)); - DLOGI("ICE Server address for %s with getaddrinfo: %s", pWebRtcClientContext->pStunIpAddrCtx->hostname, addressResolved); - pWebRtcClientContext->pStunIpAddrCtx->isIpInitialized = TRUE; + if (pWebRtcClientContext->pStunIpAddrCtx->kvsIpAddresses.ipv4Address.family != KVS_IP_FAMILY_TYPE_NOT_SET) { + // If the IPv4 family is set, then there must have been an IPv4 address resolved. + getIpAddrStr(&pWebRtcClientContext->pStunIpAddrCtx->kvsIpAddresses.ipv4Address, addressResolvedIPv4, + ARRAY_SIZE(addressResolvedIPv4)); + DLOGI("ICE Server address for %s with getaddrinfo: %s", pWebRtcClientContext->pStunIpAddrCtx->hostname, addressResolvedIPv4); + pWebRtcClientContext->pStunIpAddrCtx->isIpInitialized = TRUE; + } + if (pWebRtcClientContext->pStunIpAddrCtx->kvsIpAddresses.ipv6Address.family != KVS_IP_FAMILY_TYPE_NOT_SET) { + // If the IPv6 family is set, then there must have been an IPv6 address resolved. + getIpAddrStr(&pWebRtcClientContext->pStunIpAddrCtx->kvsIpAddresses.ipv6Address, addressResolvedIPv6, + ARRAY_SIZE(addressResolvedIPv6)); + DLOGI("ICE Server address for %s with getaddrinfo: %s", pWebRtcClientContext->pStunIpAddrCtx->hostname, addressResolvedIPv6); + pWebRtcClientContext->pStunIpAddrCtx->isIpInitialized = TRUE; + } + } else { DLOGE("Failed to resolve %s", pWebRtcClientContext->pStunIpAddrCtx->hostname); } diff --git a/src/source/PeerConnection/PeerConnection.h b/src/source/PeerConnection/PeerConnection.h index 7311ec9794..20cec6e078 100644 --- a/src/source/PeerConnection/PeerConnection.h +++ b/src/source/PeerConnection/PeerConnection.h @@ -158,7 +158,7 @@ typedef struct { typedef struct { CHAR hostname[MAX_ICE_CONFIG_URI_LEN + 1]; - KvsIpAddress kvsIpAddr; + DualKvsIpAddresses kvsIpAddresses; BOOL isIpInitialized; UINT64 startTime; UINT64 stunDnsResolutionTime; diff --git a/src/source/Signaling/ChannelInfo.c b/src/source/Signaling/ChannelInfo.c index 3aba6d57cb..989c8729d7 100644 --- a/src/source/Signaling/ChannelInfo.c +++ b/src/source/Signaling/ChannelInfo.c @@ -64,7 +64,7 @@ STATUS createValidateChannelInfo(PChannelInfo pOrigChannelInfo, PChannelInfo* pp pRegionPtr = DEFAULT_AWS_REGION; } - if (pOrigChannelInfo->pControlPlaneUrl != NULL) { + if (!IS_NULL_OR_EMPTY_STRING(pOrigChannelInfo->pControlPlaneUrl)) { CHK((cpUrlLen = (UINT32) STRNLEN(pOrigChannelInfo->pControlPlaneUrl, MAX_URI_CHAR_LEN + 1)) <= MAX_URI_CHAR_LEN, STATUS_SIGNALING_INVALID_CPL_LENGTH); } else { @@ -173,15 +173,30 @@ STATUS createValidateChannelInfo(PChannelInfo pOrigChannelInfo, PChannelInfo* pp pChannelInfo->pRegion = pCurPtr; pCurPtr += ALIGN_UP_TO_MACHINE_WORD(regionLen + 1); - if (pOrigChannelInfo->pControlPlaneUrl != NULL && *pOrigChannelInfo->pControlPlaneUrl != '\0') { + if (!IS_NULL_OR_EMPTY_STRING(pOrigChannelInfo->pControlPlaneUrl)) { STRCPY(pCurPtr, pOrigChannelInfo->pControlPlaneUrl); } else { - // Create a fully qualified URI - SNPRINTF(pCurPtr, MAX_CONTROL_PLANE_URI_CHAR_LEN, "%s%s.%s%s", CONTROL_PLANE_URI_PREFIX, KINESIS_VIDEO_SERVICE_NAME, pChannelInfo->pRegion, - CONTROL_PLANE_URI_POSTFIX); - // If region is in CN, add CN region uri postfix - if (STRSTR(pChannelInfo->pRegion, "cn-")) { - STRCAT(pCurPtr, ".cn"); + if (NULL != GETENV(USE_DUAL_STACK_ENDPOINTS_ENV_VAR)) { + // Create dual-stack fully qualified URI for appropriate region. + DLOGI("Using dual-stack KVS endpoints."); + if (STRSTR(pChannelInfo->pRegion, AWS_CN_REGION_PREFIX)) { + SNPRINTF(pCurPtr, MAX_CONTROL_PLANE_URI_CHAR_LEN, "%s%s.%s%s", CONTROL_PLANE_URI_PREFIX, KINESIS_VIDEO_SERVICE_NAME, + pChannelInfo->pRegion, CONTROL_PLANE_URI_POSTFIX_CN_DUAL_STACK); + } else { + SNPRINTF(pCurPtr, MAX_CONTROL_PLANE_URI_CHAR_LEN, "%s%s.%s%s", CONTROL_PLANE_URI_PREFIX, KINESIS_VIDEO_SERVICE_NAME, + pChannelInfo->pRegion, CONTROL_PLANE_URI_POSTFIX_DUAL_STACK); + } + + } else { + // Create legacy fully qualified URI for appropriate region. + DLOGI("Using legacy KVS endpoints."); + if (STRSTR(pChannelInfo->pRegion, AWS_CN_REGION_PREFIX)) { + SNPRINTF(pCurPtr, MAX_CONTROL_PLANE_URI_CHAR_LEN, "%s%s.%s%s", CONTROL_PLANE_URI_PREFIX, KINESIS_VIDEO_SERVICE_NAME, + pChannelInfo->pRegion, CONTROL_PLANE_URI_POSTFIX_CN); + } else { + SNPRINTF(pCurPtr, MAX_CONTROL_PLANE_URI_CHAR_LEN, "%s%s.%s%s", CONTROL_PLANE_URI_PREFIX, KINESIS_VIDEO_SERVICE_NAME, + pChannelInfo->pRegion, CONTROL_PLANE_URI_POSTFIX); + } } } diff --git a/src/source/Signaling/ChannelInfo.h b/src/source/Signaling/ChannelInfo.h index 604b23386e..832533fbf3 100644 --- a/src/source/Signaling/ChannelInfo.h +++ b/src/source/Signaling/ChannelInfo.h @@ -10,9 +10,6 @@ Signaling internal include file extern "C" { #endif -// Max control plane URI char len -#define MAX_CONTROL_PLANE_URI_CHAR_LEN 256 - // Max channel status string length in describe API call in chars #define MAX_DESCRIBE_CHANNEL_STATUS_LEN 32 @@ -40,6 +37,8 @@ extern "C" { #define SIGNALING_USER_AGENT_POSTFIX_VERSION (PCHAR) "UNKNOWN" #endif +#define USE_DUAL_STACK_ENDPOINTS_ENV_VAR ((PCHAR) "KVS_DUALSTACK_ENDPOINTS") + /** * Takes in a pointer to a public version of ChannelInfo object. * Validates and creates an internal object diff --git a/src/source/Stun/Stun.c b/src/source/Stun/Stun.c index 50001563cc..0cd1b335ed 100644 --- a/src/source/Stun/Stun.c +++ b/src/source/Stun/Stun.c @@ -82,6 +82,7 @@ STATUS serializeStunPacket(PStunPacket pStunPacket, PBYTE password, UINT32 passw PStunAttributeLifetime pStunAttributeLifetime; PStunAttributeChangeRequest pStunAttributeChangeRequest; PStunAttributeRequestedTransport pStunAttributeRequestedTransport; + PStunAttributeAllocationAddressFamily pStunAttributeAllocationAddressFamily; PStunAttributeRealm pStunAttributeRealm; PStunAttributeNonce pStunAttributeNonce; PStunAttributeErrorCode pStunAttributeErrorCode; @@ -265,6 +266,27 @@ STATUS serializeStunPacket(PStunPacket pStunPacket, PBYTE password, UINT32 passw break; + case STUN_ATTRIBUTE_TYPE_REQUESTED_ADDRESS_FAMILY: + + pStunAttributeAllocationAddressFamily = (PStunAttributeAllocationAddressFamily) pStunAttributeHeader; + + encodedLen = STUN_ATTRIBUTE_HEADER_LEN + STUN_ATTRIBUTE_REQUESTED_ADDRESS_FAMILY_LEN; + + CHK(!fingerprintFound && !messaageIntegrityFound, STATUS_STUN_ATTRIBUTES_AFTER_FINGERPRINT_MESSAGE_INTEGRITY); + + if (pBuffer != NULL) { + CHK(remaining >= encodedLen, STATUS_NOT_ENOUGH_MEMORY); + + // Package the message header first + PACKAGE_STUN_ATTR_HEADER(pCurrentBufferPosition, pStunAttributeHeader->type, pStunAttributeHeader->length); + + // Package the value + MEMCPY(pCurrentBufferPosition + STUN_ATTRIBUTE_HEADER_LEN, pStunAttributeAllocationAddressFamily->ipFamily, + STUN_ATTRIBUTE_REQUESTED_ADDRESS_FAMILY_LEN); + } + + break; + case STUN_ATTRIBUTE_TYPE_REALM: pStunAttributeRealm = (PStunAttributeRealm) pStunAttributeHeader; @@ -1456,6 +1478,42 @@ STATUS appendStunRealmAttribute(PStunPacket pStunPacket, PCHAR realm) return retStatus; } +STATUS appendStunAllocationAddressFamily(PStunPacket pStunPacket, KVS_IP_FAMILY_TYPE ipFamily) +{ + ENTERS(); + STATUS retStatus = STATUS_SUCCESS; + PStunAttributeAllocationAddressFamily pAttribute = NULL; + PStunAttributeHeader pAttributeHeader = NULL; + UINT16 ATTRIBUTE_LENGTH = 4; + UINT8 addressFamily = (UINT8) ipFamily; + + CHK_STATUS(getFirstAvailableStunAttribute(pStunPacket, &pAttributeHeader)); + pAttribute = (PStunAttributeAllocationAddressFamily) pAttributeHeader; + + // Validate the overall size + CHK((PBYTE) pStunPacket + pStunPacket->allocationSize >= (PBYTE) pAttribute + ROUND_UP(SIZEOF(StunAttributeAllocationAddressFamily), 4), + STATUS_NOT_ENOUGH_MEMORY); + + // Set up the new entry and copy data over + pStunPacket->attributeList[pStunPacket->attributesCount++] = (PStunAttributeHeader) pAttribute; + + pAttribute->attribute.length = ATTRIBUTE_LENGTH; + + pAttribute->attribute.type = STUN_ATTRIBUTE_TYPE_REQUESTED_ADDRESS_FAMILY; + + // Set the address family + MEMSET(pAttribute->ipFamily, 0x00, ATTRIBUTE_LENGTH); + *pAttribute->ipFamily = (BYTE) addressFamily; + + // Fix-up the STUN header message length + pStunPacket->header.messageLength += pAttribute->attribute.length + STUN_ATTRIBUTE_HEADER_LEN; + +CleanUp: + + LEAVES(); + return retStatus; +} + STATUS appendStunNonceAttribute(PStunPacket pStunPacket, PBYTE nonce, UINT16 nonceLen) { ENTERS(); diff --git a/src/source/Stun/Stun.h b/src/source/Stun/Stun.h index db5b63e517..a3f4925f10 100644 --- a/src/source/Stun/Stun.h +++ b/src/source/Stun/Stun.h @@ -72,6 +72,11 @@ extern "C" { */ #define STUN_ATTRIBUTE_REQUESTED_TRANSPORT_PROTOCOL_LEN (UINT16) 4 +/** + * Requested address family attribute value length = 4 bytes = 32 bits + */ +#define STUN_ATTRIBUTE_REQUESTED_ADDRESS_FAMILY_LEN (UINT16) 4 + /** * Candidate attribute has no size */ @@ -209,6 +214,7 @@ typedef enum { STUN_ATTRIBUTE_TYPE_REALM = (UINT16) 0x0014, STUN_ATTRIBUTE_TYPE_NONCE = (UINT16) 0x0015, STUN_ATTRIBUTE_TYPE_XOR_RELAYED_ADDRESS = (UINT16) 0x0016, + STUN_ATTRIBUTE_TYPE_REQUESTED_ADDRESS_FAMILY = 0x0017, STUN_ATTRIBUTE_TYPE_EVEN_PORT = (UINT16) 0x0018, STUN_ATTRIBUTE_TYPE_REQUESTED_TRANSPORT = (UINT16) 0x0019, STUN_ATTRIBUTE_TYPE_DONT_FRAGMENT = (UINT16) 0x001A, @@ -299,6 +305,11 @@ typedef struct { PCHAR realm; } StunAttributeRealm, *PStunAttributeRealm; +typedef struct { + StunAttributeHeader attribute; + BYTE ipFamily[4]; +} StunAttributeAllocationAddressFamily, *PStunAttributeAllocationAddressFamily; + typedef struct { StunAttributeHeader attribute; @@ -379,6 +390,7 @@ STATUS appendStunPriorityAttribute(PStunPacket, UINT32); STATUS appendStunLifetimeAttribute(PStunPacket, UINT32); STATUS appendStunRequestedTransportAttribute(PStunPacket, UINT8); STATUS appendStunRealmAttribute(PStunPacket, PCHAR); +STATUS appendStunAllocationAddressFamily(PStunPacket, KVS_IP_FAMILY_TYPE); STATUS appendStunNonceAttribute(PStunPacket, PBYTE, UINT16); STATUS updateStunNonceAttribute(PStunPacket, PBYTE, UINT16); STATUS appendStunErrorCodeAttribute(PStunPacket, PCHAR, UINT16); diff --git a/tst/CustomEndpointTest.cpp b/tst/CustomEndpointTest.cpp new file mode 100644 index 0000000000..f81427431d --- /dev/null +++ b/tst/CustomEndpointTest.cpp @@ -0,0 +1,143 @@ +#include "WebRTCClientTestFixture.h" + +namespace com { +namespace amazonaws { +namespace kinesis { +namespace video { +namespace webrtcclient { + +class CustomEndpointTest : public WebRtcClientTestBase { +}; + +TEST_F(CustomEndpointTest, customControlPlaneEndpointBasicCase) +{ + STATUS retStatus; + ChannelInfo channelInfo; + MEMSET(&channelInfo, 0, SIZEOF(channelInfo)); + PChannelInfo pChannelInfo = nullptr; + CHAR originalCustomControlPlaneUrl[MAX_CONTROL_PLANE_URI_CHAR_LEN] = "https://kinesisvideo.us-west-2.api.aws"; + CHAR validatedCustomControlPlaneUrl[MAX_CONTROL_PLANE_URI_CHAR_LEN] = {0}; + + // Make a deep copy of originalCustomControlPlaneUrl into validatedCustomControlPlaneUrl to save the value for later comparison. + strncpy(validatedCustomControlPlaneUrl, originalCustomControlPlaneUrl, MAX_CONTROL_PLANE_URI_CHAR_LEN); + validatedCustomControlPlaneUrl[MAX_CONTROL_PLANE_URI_CHAR_LEN - 1] = '\0'; + + channelInfo.pControlPlaneUrl = (PCHAR) validatedCustomControlPlaneUrl; + + // pChannelName must be set to make the createValidateChannelInfo call. + channelInfo.pChannelName = "TestChannelName"; + + EXPECT_EQ(STATUS_SUCCESS, createValidateChannelInfo(&channelInfo, &pChannelInfo)); + EXPECT_NE(nullptr, pChannelInfo); + + // Validate the ChannelInfo's Control Plane URL against the originalCustomControlPlaneUrl. + EXPECT_STREQ(pChannelInfo->pControlPlaneUrl, (PCHAR) originalCustomControlPlaneUrl); + + freeChannelInfo(&pChannelInfo); +} + +TEST_F(CustomEndpointTest, customControlPlaneEndpointEdgeCases) +{ + STATUS retStatus; + ChannelInfo channelInfo; + MEMSET(&channelInfo, 0, SIZEOF(channelInfo)); + PChannelInfo pChannelInfo = nullptr; + + // TODO: Verify why we use MAX_URI_CHAR_LEN instead of MAX_CONTROL_PLANE_URI_CHAR_LEN for the custom URL. + // MAX_URI_CHAR_LEN does not include the null terminator, so add 1. + CHAR originalCustomControlPlaneUrl[MAX_URI_CHAR_LEN + 1] = {0}; + CHAR validatedCustomControlPlaneUrl[MAX_URI_CHAR_LEN + 1] = {0}; + CHAR expectedSdkGeneratedUri[MAX_CONTROL_PLANE_URI_CHAR_LEN] = {0}; + + // If createValidateChannelInfo needs to generate the URL, it will use DEFAULT_AWS_REGION since + // we are not setting a region on ChannelInfo. + SNPRINTF(expectedSdkGeneratedUri, SIZEOF(expectedSdkGeneratedUri), "%s%s.%s%s", + CONTROL_PLANE_URI_PREFIX, KINESIS_VIDEO_SERVICE_NAME, DEFAULT_AWS_REGION, CONTROL_PLANE_URI_POSTFIX); + + channelInfo.pControlPlaneUrl = (PCHAR) validatedCustomControlPlaneUrl; + + // pChannelName must be set to make the createValidateChannelInfo call. + channelInfo.pChannelName = "TestChannelName"; + + + /* MAX URL ARRAY LENGTH (expect the custom URL be used) */ + + // Fill array full of non-null values, accounting for null terminator. + MEMSET(originalCustomControlPlaneUrl, 'X', SIZEOF(originalCustomControlPlaneUrl)); + originalCustomControlPlaneUrl[SIZEOF(originalCustomControlPlaneUrl) - 1] = '\0'; + + // Make a deep copy of originalCustomControlPlaneUrl into validatedCustomControlPlaneUrl to save the value for + // later comparison in case createValidateChannelInfo modified validatedCustomControlPlaneUrl. + strncpy(validatedCustomControlPlaneUrl, originalCustomControlPlaneUrl, SIZEOF(validatedCustomControlPlaneUrl)); + validatedCustomControlPlaneUrl[SIZEOF(validatedCustomControlPlaneUrl) - 1] = '\0'; + + channelInfo.pControlPlaneUrl = (PCHAR) validatedCustomControlPlaneUrl; + + EXPECT_EQ(STATUS_SUCCESS, createValidateChannelInfo(&channelInfo, &pChannelInfo)); + EXPECT_NE(nullptr, pChannelInfo); + + // Validate the ChannelInfo's Control Plane URL against originalCustomControlPlaneUrl in case createValidateChannelInfo + // modified validatedCustomControlPlaneUrl. + EXPECT_STREQ(pChannelInfo->pControlPlaneUrl, (PCHAR) originalCustomControlPlaneUrl); + freeChannelInfo(&pChannelInfo); + + + /* EMPTY URL ARRAY (expect SDK to construct a proper URL) */ + + // Fill array with null values. + MEMSET(validatedCustomControlPlaneUrl, 0, SIZEOF(validatedCustomControlPlaneUrl)); + + channelInfo.pControlPlaneUrl = (PCHAR) validatedCustomControlPlaneUrl; + + EXPECT_EQ(STATUS_SUCCESS, createValidateChannelInfo(&channelInfo, &pChannelInfo)); + EXPECT_NE(nullptr, pChannelInfo); + + // Validate the ChannelInfo's Control Plane URL should be the default one the SDK generated. + EXPECT_STREQ(pChannelInfo->pControlPlaneUrl, (PCHAR) expectedSdkGeneratedUri); + freeChannelInfo(&pChannelInfo); + + + /* NULL URL ARRAY (expect SDK to construct a proper URL) */ + + channelInfo.pControlPlaneUrl = nullptr; + + EXPECT_EQ(STATUS_SUCCESS, createValidateChannelInfo(&channelInfo, &pChannelInfo)); + EXPECT_NE(nullptr, pChannelInfo); + + EXPECT_STREQ(pChannelInfo->pControlPlaneUrl, (PCHAR) expectedSdkGeneratedUri); + freeChannelInfo(&pChannelInfo); +} + + +TEST_F(CustomEndpointTest, customControlPlaneEndpointTooLong) +{ + STATUS retStatus; + ChannelInfo channelInfo; + MEMSET(&channelInfo, 0, SIZEOF(channelInfo)); + PChannelInfo pChannelInfo = nullptr; + + + /* Exceed MAX URL ARRAY LENGTH (expect SDK to error out) */ + + // MAX_URI_CHAR_LEN does not include the null terminator, so add 2. + CHAR customControlPlaneUrl[MAX_URI_CHAR_LEN + 2] = {0}; + + // pChannelName must be set to make the createValidateChannelInfo call. + channelInfo.pChannelName = "TestChannelName"; + + // Fill array full of non-null values, accounting for null terminator. + MEMSET(customControlPlaneUrl, 'X', SIZEOF(customControlPlaneUrl)); + customControlPlaneUrl[SIZEOF(customControlPlaneUrl) - 1] = '\0'; + + channelInfo.pControlPlaneUrl = (PCHAR) customControlPlaneUrl; + + EXPECT_EQ(STATUS_SIGNALING_INVALID_CPL_LENGTH, createValidateChannelInfo(&channelInfo, &pChannelInfo)); + freeChannelInfo(&pChannelInfo); +} + + +} // namespace webrtcclient +} // namespace video +} // namespace kinesis +} // namespace amazonaws +} // namespace com diff --git a/tst/DualStackEndpointsTest.cpp b/tst/DualStackEndpointsTest.cpp new file mode 100644 index 0000000000..5c5fac92c5 --- /dev/null +++ b/tst/DualStackEndpointsTest.cpp @@ -0,0 +1,52 @@ +#include "WebRTCClientTestFixture.h" + +namespace com { +namespace amazonaws { +namespace kinesis { +namespace video { +namespace webrtcclient { + +class DualStackEndpointsTest : public WebRtcClientTestBase {}; + + +TEST_F(DualStackEndpointsTest, connectTwoDualStackPeersWithForcedTurn) +{ + // (This fails because service is not yet ready.) + + + // RtcConfiguration configuration; + // PRtcPeerConnection offerPc = NULL, answerPc = NULL; + + // ASSERT_EQ(TRUE, mAccessKeyIdSet); + + // setenv(USE_DUAL_STACK_ENDPOINTS_ENV_VAR, "ON", 1); + + // MEMSET(&configuration, 0x00, SIZEOF(RtcConfiguration)); + // configuration.iceTransportPolicy = ICE_TRANSPORT_POLICY_RELAY; + + // initializeSignalingClient(); + // getIceServers(&configuration); + + // EXPECT_EQ(createPeerConnection(&configuration, &offerPc), STATUS_SUCCESS); + // EXPECT_EQ(createPeerConnection(&configuration, &answerPc), STATUS_SUCCESS); + + // EXPECT_EQ(connectTwoPeers(offerPc, answerPc), TRUE); + + // closePeerConnection(offerPc); + // closePeerConnection(answerPc); + + // freePeerConnection(&offerPc); + // freePeerConnection(&answerPc); + + // deinitializeSignalingClient(); + + // unsetenv(USE_DUAL_STACK_ENDPOINTS_ENV_VAR); +} + + + +} // namespace webrtcclient +} // namespace video +} // namespace kinesis +} // namespace amazonaws +} // namespace com \ No newline at end of file diff --git a/tst/IceFunctionalityTest.cpp b/tst/IceFunctionalityTest.cpp index 3a27b6324b..1c9d5fc8cf 100644 --- a/tst/IceFunctionalityTest.cpp +++ b/tst/IceFunctionalityTest.cpp @@ -305,7 +305,7 @@ TEST_F(IceFunctionalityTest, IceAgentIceAgentAddIceServerUnitTest) EXPECT_EQ(STATUS_SUCCESS, parseIceServer(&iceServer, (PCHAR) "turn:54.202.170.151:443?transport=udp", (PCHAR) "username", (PCHAR) "password")); EXPECT_TRUE(!iceServer.isSecure); EXPECT_EQ(iceServer.transport, KVS_SOCKET_PROTOCOL_UDP); - EXPECT_EQ(443, (UINT16) getInt16(iceServer.ipAddress.port)); + EXPECT_EQ(443, (UINT16) getInt16(iceServer.ipAddresses.ipv4Address.port)); /* we are not doing full validation. Only parsing out what we know */ EXPECT_EQ(STATUS_SUCCESS, parseIceServer(&iceServer, (PCHAR) "turn:54.202.170.151:443?randomstuff", (PCHAR) "username", (PCHAR) "password")); diff --git a/tst/NetworkApiTest.cpp b/tst/NetworkApiTest.cpp index 54dc6ad9db..bea64eda4b 100644 --- a/tst/NetworkApiTest.cpp +++ b/tst/NetworkApiTest.cpp @@ -11,14 +11,20 @@ class NetworkApiTest : public WebRtcClientTestBase { TEST_F(NetworkApiTest, GetIpWithHostNameTest) { - KvsIpAddress ipAddress; - EXPECT_EQ(STATUS_NULL_ARG, getIpWithHostName(NULL, &ipAddress)); - EXPECT_EQ(STATUS_RESOLVE_HOSTNAME_FAILED, getIpWithHostName((PCHAR) "stun:stun.test.net:3478", &ipAddress)); - EXPECT_EQ(STATUS_SUCCESS, getIpWithHostName((PCHAR) "35-90-63-38.t-ae7dd61a.kinesisvideo.us-west-2.amazonaws.com", &ipAddress)); - EXPECT_EQ(STATUS_SUCCESS, getIpWithHostName((PCHAR) "12.34.45.40", &ipAddress)); - EXPECT_EQ(STATUS_SUCCESS, getIpWithHostName((PCHAR) "2001:0db8:85a3:0000:0000:8a2e:0370:7334", &ipAddress)); - EXPECT_EQ(STATUS_RESOLVE_HOSTNAME_FAILED, getIpWithHostName((PCHAR) ".12.34.45.40", &ipAddress)); - EXPECT_EQ(STATUS_RESOLVE_HOSTNAME_FAILED, getIpWithHostName((PCHAR) "...........", &ipAddress)); + DualKvsIpAddresses ipAddresses; + EXPECT_EQ(STATUS_NULL_ARG, getIpWithHostName(NULL, &ipAddresses)); + EXPECT_EQ(STATUS_RESOLVE_HOSTNAME_FAILED, getIpWithHostName((PCHAR) "stun:stun.test.net:3478", &ipAddresses)); + EXPECT_EQ(STATUS_SUCCESS, getIpWithHostName((PCHAR) "35-90-63-38.t-ae7dd61a.kinesisvideo.us-west-2.amazonaws.com", &ipAddresses)); + + // Test dual-stack TURN server hostname parsing with the dual-stack envvar set. + setenv(USE_DUAL_STACK_ENDPOINTS_ENV_VAR, "ON", 1); + EXPECT_EQ(STATUS_SUCCESS, getIpWithHostName((PCHAR) "35-90-63-38_2001-0db8-85a3-0000-0000-8a2e-0370-7334.t-ae7dd61a.kinesisvideo.us-west-2.api.aws", &ipAddresses)); + unsetenv(USE_DUAL_STACK_ENDPOINTS_ENV_VAR); + + EXPECT_EQ(STATUS_SUCCESS, getIpWithHostName((PCHAR) "12.34.45.40", &ipAddresses)); + EXPECT_EQ(STATUS_SUCCESS, getIpWithHostName((PCHAR) "2001:0db8:85a3:0000:0000:8a2e:0370:7334", &ipAddresses)); + EXPECT_EQ(STATUS_RESOLVE_HOSTNAME_FAILED, getIpWithHostName((PCHAR) ".12.34.45.40", &ipAddresses)); + EXPECT_EQ(STATUS_RESOLVE_HOSTNAME_FAILED, getIpWithHostName((PCHAR) "...........", &ipAddresses)); } TEST_F(NetworkApiTest, ipIpAddrTest) diff --git a/tst/TurnConnectionFunctionalityTest.cpp b/tst/TurnConnectionFunctionalityTest.cpp index da2914cd63..e9bb9dc182 100644 --- a/tst/TurnConnectionFunctionalityTest.cpp +++ b/tst/TurnConnectionFunctionalityTest.cpp @@ -52,7 +52,7 @@ class TurnConnectionFunctionalityTest : public WebRtcClientTestBase { EXPECT_EQ(STATUS_SUCCESS, getLocalhostIpAddresses(localIpInterfaces, &localIpInterfaceCount, NULL, 0)); for (i = 0; i < localIpInterfaceCount; ++i) { - if (localIpInterfaces[i].family == pTurnServer->ipAddress.family && (pTurnSocketAddr == NULL || localIpInterfaces[i].isPointToPoint)) { + if (localIpInterfaces[i].family == pTurnServer->ipAddresses.ipv4Address.family && (pTurnSocketAddr == NULL || localIpInterfaces[i].isPointToPoint)) { pTurnSocketAddr = &localIpInterfaces[i]; } } @@ -69,12 +69,12 @@ class TurnConnectionFunctionalityTest : public WebRtcClientTestBase { return STATUS_SUCCESS; }; EXPECT_EQ(STATUS_SUCCESS, - createSocketConnection((KVS_IP_FAMILY_TYPE) pTurnServer->ipAddress.family, KVS_ICE_DEFAULT_TURN_PROTOCOL, NULL, - &pTurnServer->ipAddress, (UINT64) this, onDataHandler, 0, &pTurnSocket)); + createSocketConnection((KVS_IP_FAMILY_TYPE) pTurnServer->ipAddresses.ipv4Address.family, KVS_ICE_DEFAULT_TURN_PROTOCOL, NULL, + &pTurnServer->ipAddresses.ipv4Address, (UINT64) this, onDataHandler, 0, &pTurnSocket)); EXPECT_EQ(STATUS_SUCCESS, connectionListenerAddConnection(pConnectionListener, pTurnSocket)); ASSERT_EQ(STATUS_SUCCESS, createTurnConnection(pTurnServer, timerQueueHandle, TURN_CONNECTION_DATA_TRANSFER_MODE_DATA_CHANNEL, KVS_ICE_DEFAULT_TURN_PROTOCOL, - NULL, pTurnSocket, pConnectionListener, &pTurnConnection)); + NULL, pTurnSocket, pConnectionListener, KVS_IP_FAMILY_TYPE_IPV4, &pTurnConnection)); EXPECT_EQ(STATUS_SUCCESS, connectionListenerStart(pConnectionListener)); }