From ee9eb0037ccc02190e4d1fc9096d3012168798b2 Mon Sep 17 00:00:00 2001 From: Stefan Kieszkowski <85728496+stefankiesz@users.noreply.github.com> Date: Thu, 13 Nov 2025 11:05:19 -0800 Subject: [PATCH 01/17] Add support for dual-stack STUN and TURN, and for IPv6 srflx and relay ICE candidates --- .../Dependencies/libwebsockets-CMakeLists.txt | 1 + samples/Common.c | 49 ++++++-- src/source/Ice/IceAgent.c | 113 +++++++++++++----- src/source/Ice/IceAgent.h | 2 +- src/source/Ice/IceUtils.c | 33 +++-- src/source/Ice/IceUtils.h | 2 +- src/source/Ice/NatBehaviorDiscovery.c | 21 ++-- src/source/Ice/Network.c | 52 +++++--- src/source/Ice/Network.h | 2 +- src/source/Ice/SocketConnection.c | 2 +- src/source/Ice/TurnConnection.c | 27 +++-- src/source/Ice/TurnConnection.h | 2 + src/source/Ice/TurnConnectionStateMachine.c | 17 ++- src/source/Include_i.h | 37 +++++- src/source/PeerConnection/PeerConnection.c | 63 +++++++--- src/source/PeerConnection/PeerConnection.h | 2 +- src/source/Stun/Stun.c | 66 +++++++++- src/source/Stun/Stun.h | 12 ++ tst/IceFunctionalityTest.cpp | 2 +- tst/NetworkApiTest.cpp | 16 +-- tst/TurnConnectionFunctionalityTest.cpp | 6 +- 21 files changed, 404 insertions(+), 123 deletions(-) diff --git a/CMake/Dependencies/libwebsockets-CMakeLists.txt b/CMake/Dependencies/libwebsockets-CMakeLists.txt index c040ea2dda..d0a756eb1d 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_IPV6=1 BUILD_ALWAYS TRUE TEST_COMMAND "" ) diff --git a/samples/Common.c b/samples/Common.c index b371f30779..a3c66c0060 100644 --- a/samples/Common.c +++ b/samples/Common.c @@ -345,6 +345,26 @@ VOID onIceCandidateHandler(UINT64 customData, PCHAR candidateJson) CHK_LOG_ERR(retStatus); } + +// Using for testing purposes to filter out tcp and turns cases. +BOOL isTurnUdp(const char* url) { + size_t len = STRLEN(url); + if (len < 7) return FALSE; + + // Check start: "turn:" + if (STRNCMP(url, "turn:", 5) != 0) { + return FALSE; + } + + // Check end: "udp" + if (STRCMP(url + len - 3, "udp") != 0) { + return FALSE; + } + + return TRUE; +} + + STATUS initializePeerConnection(PSampleConfiguration pSampleConfiguration, PRtcPeerConnection* ppRtcPeerConnection) { ENTERS(); @@ -363,7 +383,7 @@ STATUS initializePeerConnection(PSampleConfiguration pSampleConfiguration, PRtcP configuration.kvsRtcConfiguration.iceSetInterfaceFilterFunc = NULL; // Set the ICE mode explicitly - configuration.iceTransportPolicy = ICE_TRANSPORT_POLICY_ALL; + configuration.iceTransportPolicy = ICE_TRANSPORT_POLICY_RELAY; #ifdef ENABLE_STATS_CALCULATION_CONTROL configuration.kvsRtcConfiguration.enableIceStats = pSampleConfiguration->enableIceStats; @@ -379,7 +399,7 @@ STATUS initializePeerConnection(PSampleConfiguration pSampleConfiguration, PRtcP pKinesisVideoStunUrlPostFix); if (pSampleConfiguration->useTurn) { - // Set the URIs from the configuration + // // Set the URIs from the configuration CHK_STATUS(signalingClientGetIceConfigInfoCount(pSampleConfiguration->signalingClientHandle, &iceConfigCount)); /* signalingClientGetIceConfigInfoCount can return more than one turn server. Use only one to optimize @@ -398,13 +418,28 @@ 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. */ - 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); + DLOGD("Ice server %d urls: %s", j + 1, pIceConfigInfo->uris[j]); + DLOGD("Ice server %d username: %s", j + 1, pIceConfigInfo->password); + DLOGD("Ice server %d credential: %s", j + 1, pIceConfigInfo->userName); + + if (isTurnUdp(pIceConfigInfo->uris[j])) { + DLOGD("Adding ICE server"); + 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); + + uriCount++; + } + - uriCount++; } } + + // For testing purposes: + // STRNCPY(configuration.iceServers[1].urls, "turn:[XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX]?transport=udp", MAX_ICE_CONFIG_URI_LEN); + // STRNCPY(configuration.iceServers[1].urls, "turn:XXX.XXX.XXX.XXX:XXXX?transport=udp", MAX_ICE_CONFIG_URI_LEN); + // STRNCPY(configuration.iceServers[1].credential, "XXXXXXXX", MAX_ICE_CONFIG_CREDENTIAL_LEN); + // STRNCPY(configuration.iceServers[1].username, "XXXXXXXX", MAX_ICE_CONFIG_USER_NAME_LEN); } pSampleConfiguration->iceUriCount = uriCount + 1; @@ -887,7 +922,7 @@ STATUS createSampleConfiguration(PCHAR channelName, SIGNALING_CHANNEL_ROLE_TYPE pSampleConfiguration->channelInfo.pTags = NULL; pSampleConfiguration->channelInfo.channelType = SIGNALING_CHANNEL_TYPE_SINGLE_MASTER; pSampleConfiguration->channelInfo.channelRoleType = roleType; - pSampleConfiguration->channelInfo.cachingPolicy = SIGNALING_API_CALL_CACHE_TYPE_FILE; + pSampleConfiguration->channelInfo.cachingPolicy = SIGNALING_API_CALL_CACHE_TYPE_NONE; pSampleConfiguration->channelInfo.cachingPeriod = SIGNALING_API_CALL_CACHE_TTL_SENTINEL_VALUE; pSampleConfiguration->channelInfo.asyncIceServerConfig = TRUE; // has no effect pSampleConfiguration->channelInfo.retry = TRUE; diff --git a/src/source/Ice/IceAgent.c b/src/source/Ice/IceAgent.c index a9d3a066a8..565c233ac9 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); @@ -1020,7 +1021,7 @@ STATUS findCandidateWithIp(PKvsIpAddress pIpAddress, PDoubleList pCandidateList, pIceCandidate = (PIceCandidate) data; pCurNode = pCurNode->pNext; - addrLen = IS_IPV4_ADDR(pIpAddress) ? IPV4_ADDRESS_LENGTH : IPV6_ADDRESS_LENGTH; + addrLen = isIpv4Address(pIpAddress) ? IPV4_ADDRESS_LENGTH : IPV6_ADDRESS_LENGTH; if (pIpAddress->family == pIceCandidate->ipAddress.family && MEMCMP(pIceCandidate->ipAddress.address, pIpAddress->address, addrLen) == 0 && pIpAddress->port == pIceCandidate->ipAddress.port) { pTargetIceCandidate = pIceCandidate; @@ -1239,7 +1240,7 @@ STATUS findIceCandidatePairWithLocalSocketConnectionAndRemoteAddr(PIceAgent pIce CHK(pIceAgent != NULL && ppIceCandidatePair != NULL && pSocketConnection != NULL, STATUS_NULL_ARG); - addrLen = IS_IPV4_ADDR(pRemoteAddr) ? IPV4_ADDRESS_LENGTH : IPV6_ADDRESS_LENGTH; + addrLen = isIpv4Address(pRemoteAddr) ? IPV4_ADDRESS_LENGTH : IPV6_ADDRESS_LENGTH; CHK_STATUS(doubleListGetHeadNode(pIceAgent->iceCandidatePairs, &pCurNode)); while (pCurNode != NULL && pTargetIceCandidatePair == NULL) { @@ -1458,14 +1459,34 @@ 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) { + if (pIceServer->ipAddresses.ipv4Address.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)); + + DLOGD("Sending STUN binding request to IPv4 STUN server: %u:%u", pIceServer->ipAddresses.ipv4Address.address, + pIceServer->ipAddresses.ipv4Address.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())); + } + } else if (pIceServer->ipAddresses.ipv6Address.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)); + + DLOGD("Sending STUN binding request to IPv6 STUN server: %u:%u", pIceServer->ipAddresses.ipv6Address.address, + pIceServer->ipAddresses.ipv6Address.port); + + CHK_STATUS(iceAgentSendStunPacket(pBindingRequest, NULL, 0, pIceAgent, pCandidate, &pIceServer->ipAddresses.ipv6Address)); if (pIceAgent->pRtcIceServerDiagnostics[pCandidate->iceServerIndex] != NULL) { pIceAgent->pRtcIceServerDiagnostics[pCandidate->iceServerIndex]->totalRequestsSent++; CHK_STATUS(hashTableUpsert(pIceAgent->requestTimestampDiagnostics, checkSum, GETTIME())); @@ -1762,7 +1783,9 @@ 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 (!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 +1815,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)); - + if (isIpv4Address(&(pCandidate->ipAddress))) { + 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,13 +1858,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)); + + 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,12 @@ 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,10 +1936,16 @@ 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, + CHK_STATUS(createSocketConnection(turnServerIpFamily, protocol, NULL, pTurnServerAddress, (UINT64) pNewCandidate, incomingRelayedDataHandler, pIceAgent->kvsRtcConfiguration.sendBufSize, &pNewCandidate->pSocketConnection)); // connectionListener will free the pSocketConnection at the end. @@ -1934,11 +1981,17 @@ STATUS iceAgentInitRelayCandidate(PIceAgent pIceAgent, UINT32 iceServerIndex, KV CHK_STATUS(doubleListGetNodeData(pCurNode, &data)); pCurNode = pCurNode->pNext; pCandidate = (PIceCandidate) data; + + // Add only peers with matching IP family to the TURN connection. + if (turnServerIpFamily == KVS_IP_FAMILY_TYPE_IPV4) { + if (isIpv4Address(&pCandidate->ipAddress)) { + CHK_STATUS(turnConnectionAddPeer(pTurnConnection, &pCandidate->ipAddress)); + } + } else { + if (isIpv6Address(&pCandidate->ipAddress)) { + CHK_STATUS(turnConnectionAddPeer(pTurnConnection, &pCandidate->ipAddress)); + } - // 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)); } } @@ -2423,7 +2476,7 @@ STATUS incomingDataHandler(UINT64 customData, PSocketConnection pSocketConnectio MUTEX_LOCK(pIceAgent->lock); locked = TRUE; - addrLen = IS_IPV4_ADDR(pSrc) ? IPV4_ADDRESS_LENGTH : IPV6_ADDRESS_LENGTH; + addrLen = isIpv4Address(pSrc) ? IPV4_ADDRESS_LENGTH : IPV6_ADDRESS_LENGTH; if (pIceAgent->pDataSendingIceCandidatePair != NULL && pIceAgent->pDataSendingIceCandidatePair->local->pSocketConnection == pSocketConnection && pIceAgent->pDataSendingIceCandidatePair->remote->ipAddress.family == pSrc->family && @@ -2461,7 +2514,7 @@ STATUS iceCandidateSerialize(PIceCandidate pIceCandidate, PCHAR pOutputData, PUI CHK(pIceCandidate != NULL && pOutputLength != NULL, STATUS_NULL_ARG); // TODO FIXME real source of randomness - if (IS_IPV4_ADDR(&(pIceCandidate->ipAddress))) { + if (isIpv4Address(&(pIceCandidate->ipAddress))) { amountWritten = SNPRINTF(pOutputData, pOutputData == NULL ? 0 : *pOutputLength, "%u 1 udp %u %d.%d.%d.%d %d typ %s raddr 0.0.0.0 rport 0 generation 0 network-cost 999", pIceCandidate->foundation, pIceCandidate->priority, pIceCandidate->ipAddress.address[0], pIceCandidate->ipAddress.address[1], 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..9209639aec 100644 --- a/src/source/Ice/IceUtils.c +++ b/src/source/Ice/IceUtils.c @@ -168,16 +168,18 @@ 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) { case STUN_PACKET_TYPE_BINDING_REQUEST: - DLOGD("Sending BINDING_REQUEST on socket id: %d, to ip:%u.%u.%u.%u, port:%u", pSocketConnection->localSocket, pDest->address[0], - pDest->address[1], pDest->address[2], pDest->address[3], (UINT16) getInt16(pDest->port)); + DLOGD("Sending BINDING_REQUEST to ip:%s, port:%u", ipAddrStr, (UINT16) getInt16(pDest->port)); break; case STUN_PACKET_TYPE_BINDING_RESPONSE_SUCCESS: - DLOGD("Sending BINDING_RESPONSE_SUCCESS on socket id: %d to ip:%u.%u.%u.%u, port:%u", pSocketConnection->localSocket, pDest->address[0], - pDest->address[1], pDest->address[2], pDest->address[3], (UINT16) getInt16(pDest->port)); + DLOGD("Sending BINDING_RESPONSE_SUCCESS to ip:%s, port:%u", ipAddrStr, (UINT16) getInt16(pDest->port)); break; default: break; @@ -221,7 +223,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 +267,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 +277,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))); + DLOGP("Resolved ICE Server IPv4 address for %s: %s", pIceServer->url, addressResolvedIPv4); + DLOGP("...with port: %u", 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))); + DLOGP("Resolved ICE Server IPv6 address for %s: %s", pIceServer->url, addressResolvedIPv6); + DLOGP("...with port: %u", 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..930321a1d0 100644 --- a/src/source/Ice/Network.c +++ b/src/source/Ice/Network.c @@ -269,7 +269,7 @@ STATUS socketBind(PKvsIpAddress pHostIpAddress, INT32 sockfd) if (bind(sockfd, sockAddr, addrLen) < 0) { CHK_STATUS(getIpAddrStr(pHostIpAddress, ipAddrStr, ARRAY_SIZE(ipAddrStr))); - DLOGW("bind() failed for ip%s address: %s, port %u with errno %s", IS_IPV4_ADDR(pHostIpAddress) ? EMPTY_STRING : "V6", ipAddrStr, + DLOGW("bind() failed for ip%s address: %s, port %u with errno %s", isIpv4Address(pHostIpAddress) ? EMPTY_STRING : "V6", ipAddrStr, (UINT16) getInt16(pHostIpAddress->port), getErrorString(getErrorCode())); CHK(FALSE, STATUS_BINDING_SOCKET_FAILED); } @@ -392,17 +392,20 @@ STATUS getIpAddrFromDnsHostname(PCHAR hostname, PCHAR address, UINT16 lengthSrc, return retStatus; } -STATUS getIpWithHostName(PCHAR hostname, PKvsIpAddress destIp) +STATUS getIpWithHostName(PCHAR hostname, PDualKvsIpAddresses destIps) { STATUS retStatus = STATUS_SUCCESS; INT32 errCode; UINT16 hostnameLen, addrLen; PCHAR errStr; struct addrinfo *res, *rp; - BOOL resolved = FALSE; + BOOL ipv4Resolved = FALSE; + BOOL ipv6Resolved = FALSE; struct sockaddr_in* ipv4Addr; struct sockaddr_in6* ipv6Addr; struct in_addr inaddr; + CHAR ipv4AddrStr[KVS_IP_ADDRESS_STRING_BUFFER_LEN]; + CHAR ipv6AddrStr[KVS_IP_ADDRESS_STRING_BUFFER_LEN]; CHAR addr[KVS_IP_ADDRESS_STRING_BUFFER_LEN + 1] = {'\0'}; BOOL isStunServer; @@ -435,27 +438,40 @@ 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) { + for (rp = res; rp != NULL && !(ipv4Resolved && ipv6Resolved); 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; + destIps->ipv4Address.family = KVS_IP_FAMILY_TYPE_IPV4; + MEMCPY(destIps->ipv4Address.address, &ipv4Addr->sin_addr, IPV4_ADDRESS_LENGTH); + + CHK_STATUS(getIpAddrStr(&(destIps->ipv4Address), ipv4AddrStr, ARRAY_SIZE(ipv4AddrStr))); + DLOGD("Found an IPv4 ICE server addresss: %s", ipv4AddrStr); + + ipv4Resolved = 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; + destIps->ipv6Address.family = KVS_IP_FAMILY_TYPE_IPV6; + MEMCPY(destIps->ipv6Address.address, &ipv6Addr->sin6_addr, IPV6_ADDRESS_LENGTH); + + CHK_STATUS(getIpAddrStr(&(destIps->ipv6Address), ipv6AddrStr, ARRAY_SIZE(ipv6AddrStr))); + DLOGD("Found an IPv6 ICE server addresss: %s", ipv6AddrStr); + + ipv6Resolved = TRUE; + + } else { + DLOGD("Found an invalid ICE server addresss family type - must be IPv4 or IPv6."); } } freeaddrinfo(res); - CHK_ERR(resolved, STATUS_HOSTNAME_NOT_FOUND, "Could not find network address of %s", hostname); - } - - else { + CHK_ERR(ipv4Resolved || ipv6Resolved, STATUS_HOSTNAME_NOT_FOUND, "Could not find network address of %s", hostname); + } else { + // TODO: The below is for TURN case, will need to do this based on IP family too... + // NOTE: Current design plan is to not send TURN host names with prepended IP addressses, so this will not need to change + // and will remain for backwards compat with the legacy servers. inet_pton(AF_INET, addr, &inaddr); - destIp->family = KVS_IP_FAMILY_TYPE_IPV4; - MEMCPY(destIp->address, &inaddr, IPV4_ADDRESS_LENGTH); + destIps->ipv4Address.family = KVS_IP_FAMILY_TYPE_IPV4; + MEMCPY(destIps->ipv4Address.address, &inaddr, IPV4_ADDRESS_LENGTH); } CleanUp: @@ -471,7 +487,7 @@ STATUS getIpAddrStr(PKvsIpAddress pKvsIpAddress, PCHAR pBuffer, UINT32 bufferLen CHK(pKvsIpAddress != NULL, STATUS_NULL_ARG); CHK(pBuffer != NULL && bufferLen > 0, STATUS_INVALID_ARG); - if (IS_IPV4_ADDR(pKvsIpAddress)) { + if (isIpv4Address(pKvsIpAddress)) { generatedStrLen = SNPRINTF(pBuffer, bufferLen, "%u.%u.%u.%u", pKvsIpAddress->address[0], pKvsIpAddress->address[1], pKvsIpAddress->address[2], pKvsIpAddress->address[3]); } else { @@ -499,7 +515,7 @@ BOOL isSameIpAddress(PKvsIpAddress pAddr1, PKvsIpAddress pAddr2, BOOL checkPort) return FALSE; } - addrLen = IS_IPV4_ADDR(pAddr1) ? IPV4_ADDRESS_LENGTH : IPV6_ADDRESS_LENGTH; + addrLen = isIpv4Address(pAddr1) ? IPV4_ADDRESS_LENGTH : IPV6_ADDRESS_LENGTH; ret = (pAddr1->family == pAddr2->family && MEMCMP(pAddr1->address, pAddr2->address, addrLen) == 0 && (!checkPort || pAddr1->port == pAddr2->port)); 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/SocketConnection.c b/src/source/Ice/SocketConnection.c index af8305ef79..cf3cf61d3f 100644 --- a/src/source/Ice/SocketConnection.c +++ b/src/source/Ice/SocketConnection.c @@ -379,7 +379,7 @@ STATUS socketSendDataWithRetry(PSocketConnection pSocketConnection, PBYTE buf, U CHK(buf != NULL && bufLen > 0, STATUS_INVALID_ARG); if (pDestIp != NULL) { - if (IS_IPV4_ADDR(pDestIp)) { + if (isIpv4Address(pDestIp)) { addrLen = SIZEOF(ipv4Addr); MEMSET(&ipv4Addr, 0x00, SIZEOF(ipv4Addr)); ipv4Addr.sin_family = AF_INET; diff --git a/src/source/Ice/TurnConnection.c b/src/source/Ice/TurnConnection.c index e3e799a616..996499af52 100644 --- a/src/source/Ice/TurnConnection.c +++ b/src/source/Ice/TurnConnection.c @@ -730,8 +730,8 @@ 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"); + // CHK(pTurnConnection->turnServer.ipAddresses.ipv4Address.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"); MUTEX_LOCK(pTurnConnection->lock); locked = TRUE; @@ -754,7 +754,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 +781,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,7 +827,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, + getTurnConnectionIpAddress(pTurnConnection, pTurnServerIp); + + retStatus = iceUtilsSendData(pTurnConnection->sendDataBuffer, paddedDataLen, pTurnServerIp, pTurnConnection->pControlChannel, NULL, FALSE); if (STATUS_FAILED(retStatus)) { @@ -895,6 +898,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,8 +917,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, + ARRAY_SIZE(pTurnConnection->longTermKey), pTurnServerIp, pTurnConnection->pControlChannel, NULL, FALSE)); pTurnConnection->nextAllocationRefreshTime = currTime + DEFAULT_TURN_SEND_REFRESH_INVERVAL; @@ -1087,6 +1093,7 @@ STATUS checkTurnPeerConnections(PTurnConnection pTurnConnection) PStunAttributeAddress pStunAttributeAddress = NULL; PStunAttributeChannelNumber pStunAttributeChannelNumber = NULL; UINT32 i = 0; + PKvsIpAddress pTurnServerIp = NULL; UNUSED_PARAM(sendStatus); @@ -1111,8 +1118,9 @@ STATUS checkTurnPeerConnections(PTurnConnection pTurnConnection) CHK(pTurnPeer->pTransactionIdStore != NULL, STATUS_INVALID_OPERATION); transactionIdStoreInsert(pTurnPeer->pTransactionIdStore, pTurnConnection->pTurnCreatePermissionPacket->header.transactionId); + getTurnConnectionIpAddress(pTurnConnection, pTurnServerIp); sendStatus = iceUtilsSendStunPacket(pTurnConnection->pTurnCreatePermissionPacket, pTurnConnection->longTermKey, - ARRAY_SIZE(pTurnConnection->longTermKey), &pTurnConnection->turnServer.ipAddress, + ARRAY_SIZE(pTurnConnection->longTermKey), pTurnServerIp, pTurnConnection->pControlChannel, NULL, FALSE); } else if (pTurnPeer->connectionState == TURN_PEER_CONN_STATE_BIND_CHANNEL) { @@ -1137,8 +1145,9 @@ STATUS checkTurnPeerConnections(PTurnConnection pTurnConnection) CHK(pTurnPeer->pTransactionIdStore != NULL, STATUS_INVALID_OPERATION); transactionIdStoreInsert(pTurnPeer->pTransactionIdStore, pTurnConnection->pTurnChannelBindPacket->header.transactionId); + getTurnConnectionIpAddress(pTurnConnection, pTurnServerIp); sendStatus = iceUtilsSendStunPacket(pTurnConnection->pTurnChannelBindPacket, pTurnConnection->longTermKey, - ARRAY_SIZE(pTurnConnection->longTermKey), &pTurnConnection->turnServer.ipAddress, + ARRAY_SIZE(pTurnConnection->longTermKey), pTurnServerIp, pTurnConnection->pControlChannel, NULL, FALSE); } } @@ -1227,6 +1236,10 @@ STATUS turnConnectionPackageTurnAllocationRequest(PCHAR username, PCHAR realm, P CHK_STATUS(appendStunUsernameAttribute(pTurnAllocateRequest, username)); CHK_STATUS(appendStunRealmAttribute(pTurnAllocateRequest, realm)); CHK_STATUS(appendStunNonceAttribute(pTurnAllocateRequest, nonce, nonceLen)); + // CHK_STATUS(appendStunAllocationAddressFamily(pTurnAllocateRequest, KVS_IP_FAMILY_TYPE_IPV4)); + // Note: No longer planning to use this attribute, IP-family will be determined + // to match the socket connection between client and TURN server. + } CleanUp: diff --git a/src/source/Ice/TurnConnection.h b/src/source/Ice/TurnConnection.h index 6d73d8b336..e803f8d494 100644 --- a/src/source/Ice/TurnConnection.h +++ b/src/source/Ice/TurnConnection.h @@ -183,6 +183,8 @@ struct __TurnConnection { BOOL deallocatePacketSent; TurnProfileDiagnostics turnProfileDiagnostics; PStateMachine pStateMachine; + + KVS_IP_FAMILY_TYPE ipFamilyType; }; typedef struct __TurnConnection* PTurnConnection; diff --git a/src/source/Ice/TurnConnectionStateMachine.c b/src/source/Ice/TurnConnectionStateMachine.c index 6b7ba5775c..5e66a46b90 100644 --- a/src/source/Ice/TurnConnectionStateMachine.c +++ b/src/source/Ice/TurnConnectionStateMachine.c @@ -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,10 @@ 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)); + + getTurnConnectionIpAddress(pTurnConnection, pTurnServerIp); + CHK_STATUS(iceUtilsSendStunPacket(pTurnConnection->pTurnPacket, NULL, 0, pTurnServerIp, + pTurnConnection->pControlChannel, NULL, FALSE)); CleanUp: @@ -329,6 +332,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(); @@ -347,8 +352,10 @@ STATUS executeAllocationTurnState(UINT64 customData, UINT64 time) } 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 +676,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,8 +686,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, + 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..38fc9a7d96 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,44 @@ 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 isIpv4Address(const KvsIpAddress* pAddress) +{ + return pAddress != NULL && pAddress->family == KVS_IP_FAMILY_TYPE_IPV4; +} + +static inline BOOL isIpv6Address(const KvsIpAddress* pAddress) +{ + return pAddress != NULL && pAddress->family == KVS_IP_FAMILY_TYPE_IPV6; +} + +static inline STATUS getTurnConnectionIpAddress(PTurnConnection pTurnConnection, PKvsIpAddress pTurnConnectionIp) +{ + STATUS retStatus = STATUS_SUCCESS; + + CHK(pTurnConnection != NULL && pTurnConnectionIp != NULL, STATUS_NULL_ARG); + + if (pTurnConnection->ipFamilyType == KVS_IP_FAMILY_TYPE_IPV4) { + *pTurnConnectionIp = pTurnConnection->turnServer.ipAddresses.ipv4Address; + } else { + *pTurnConnectionIp = pTurnConnection->turnServer.ipAddresses.ipv6Address; + } +CleanUp: + CHK_LOG_ERR(retStatus); + return retStatus; +} + // 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..c1fcbfe355 100644 --- a/src/source/PeerConnection/PeerConnection.c +++ b/src/source/PeerConnection/PeerConnection.c @@ -806,25 +806,44 @@ 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; + + 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 +852,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 +882,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 +908,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 +935,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/Stun/Stun.c b/src/source/Stun/Stun.c index 50001563cc..4dd7c8c6e1 100644 --- a/src/source/Stun/Stun.c +++ b/src/source/Stun/Stun.c @@ -25,7 +25,7 @@ STATUS stunPackageIpAddr(PStunHeader pStunHeader, STUN_ATTRIBUTE_TYPE type, PKvs * - 4 byte or 16 byte ip address */ dataLen += STUN_ATTRIBUTE_HEADER_LEN + STUN_ATTRIBUTE_ADDRESS_HEADER_LEN; - dataLen += IS_IPV4_ADDR(pIndirected) ? IPV4_ADDRESS_LENGTH : IPV6_ADDRESS_LENGTH; + dataLen += isIpv4Address(pIndirected) ? IPV4_ADDRESS_LENGTH : IPV6_ADDRESS_LENGTH; // Check if we are asked for size only and early return if so CHK(pBuffer != NULL, STATUS_SUCCESS); @@ -55,7 +55,7 @@ STATUS stunPackageIpAddr(PStunHeader pStunHeader, STUN_ATTRIBUTE_TYPE type, PKvs MEMCPY(pCurrentBufferPosition, (PBYTE) &pIndirected->port, SIZEOF(pIndirected->port)); pCurrentBufferPosition += SIZEOF(pIndirected->port); - MEMCPY(pCurrentBufferPosition, pIndirected->address, IS_IPV4_ADDR(pIndirected) ? IPV4_ADDRESS_LENGTH : IPV6_ADDRESS_LENGTH); + MEMCPY(pCurrentBufferPosition, pIndirected->address, isIpv4Address(pIndirected) ? IPV4_ADDRESS_LENGTH : IPV6_ADDRESS_LENGTH); CleanUp: @@ -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; @@ -1141,7 +1163,7 @@ STATUS appendStunAddressAttribute(PStunPacket pStunPacket, STUN_ATTRIBUTE_TYPE a // Set up the new entry and copy data over pStunPacket->attributeList[pStunPacket->attributesCount++] = (PStunAttributeHeader) pAttribute; - pAttribute->attribute.length = STUN_ATTRIBUTE_ADDRESS_HEADER_LEN + (IS_IPV4_ADDR(pAddress) ? IPV4_ADDRESS_LENGTH : IPV6_ADDRESS_LENGTH); + pAttribute->attribute.length = STUN_ATTRIBUTE_ADDRESS_HEADER_LEN + (isIpv4Address(pAddress) ? IPV4_ADDRESS_LENGTH : IPV6_ADDRESS_LENGTH); pAttribute->attribute.type = addressAttributeType; // Copy the attribute entirely @@ -1234,7 +1256,7 @@ STATUS xorIpAddress(PKvsIpAddress pAddress, PBYTE pTransactionId) UINT32 i; CHK(pAddress != NULL, STATUS_NULL_ARG); - CHK(IS_IPV4_ADDR(pAddress) || pTransactionId != NULL, STATUS_INVALID_ARG); + CHK(isIpv4Address(pAddress) || pTransactionId != NULL, STATUS_INVALID_ARG); // Perform the XOR-ing pAddress->port = (UINT16) (getInt16(STUN_HEADER_MAGIC_COOKIE >> 16)) ^ pAddress->port; @@ -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..10cc66d2d5 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 +/**f + * 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/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..d958fe64c4 100644 --- a/tst/NetworkApiTest.cpp +++ b/tst/NetworkApiTest.cpp @@ -11,14 +11,14 @@ 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)); + 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..42a158b42a 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,8 +69,8 @@ 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, From abb6c6cd65360350d261e852b1649fec76287f75 Mon Sep 17 00:00:00 2001 From: Stefan Kieszkowski <85728496+stefankiesz@users.noreply.github.com> Date: Thu, 13 Nov 2025 11:23:54 -0800 Subject: [PATCH 02/17] Move getTurnConnectionIpAddress definition --- src/source/Ice/TurnConnection.c | 16 ++++++++++++++++ src/source/Ice/TurnConnection.h | 2 ++ src/source/Include_i.h | 16 ---------------- 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/src/source/Ice/TurnConnection.c b/src/source/Ice/TurnConnection.c index 996499af52..1411bfd2e5 100644 --- a/src/source/Ice/TurnConnection.c +++ b/src/source/Ice/TurnConnection.c @@ -1294,3 +1294,19 @@ VOID turnConnectionFatalError(PTurnConnection pTurnConnection, STATUS errorStatu pTurnConnection->errorStatus = errorStatus; pTurnConnection->state = TURN_STATE_FAILED; } + +STATUS getTurnConnectionIpAddress(PTurnConnection pTurnConnection, PKvsIpAddress pTurnConnectionIp) +{ + STATUS retStatus = STATUS_SUCCESS; + + CHK(pTurnConnection != NULL && pTurnConnectionIp != NULL, STATUS_NULL_ARG); + + if (pTurnConnection->ipFamilyType == KVS_IP_FAMILY_TYPE_IPV4) { + *pTurnConnectionIp = pTurnConnection->turnServer.ipAddresses.ipv4Address; + } else { + *pTurnConnectionIp = pTurnConnection->turnServer.ipAddresses.ipv6Address; + } +CleanUp: + CHK_LOG_ERR(retStatus); + return retStatus; +} diff --git a/src/source/Ice/TurnConnection.h b/src/source/Ice/TurnConnection.h index e803f8d494..cc07d55a2c 100644 --- a/src/source/Ice/TurnConnection.h +++ b/src/source/Ice/TurnConnection.h @@ -220,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/Include_i.h b/src/source/Include_i.h index 38fc9a7d96..b868d72339 100644 --- a/src/source/Include_i.h +++ b/src/source/Include_i.h @@ -126,22 +126,6 @@ static inline BOOL isIpv6Address(const KvsIpAddress* pAddress) return pAddress != NULL && pAddress->family == KVS_IP_FAMILY_TYPE_IPV6; } -static inline STATUS getTurnConnectionIpAddress(PTurnConnection pTurnConnection, PKvsIpAddress pTurnConnectionIp) -{ - STATUS retStatus = STATUS_SUCCESS; - - CHK(pTurnConnection != NULL && pTurnConnectionIp != NULL, STATUS_NULL_ARG); - - if (pTurnConnection->ipFamilyType == KVS_IP_FAMILY_TYPE_IPV4) { - *pTurnConnectionIp = pTurnConnection->turnServer.ipAddresses.ipv4Address; - } else { - *pTurnConnectionIp = pTurnConnection->turnServer.ipAddresses.ipv6Address; - } -CleanUp: - CHK_LOG_ERR(retStatus); - return retStatus; -} - // Used for ensuring alignment #define ALIGN_UP_TO_MACHINE_WORD(x) ROUND_UP((x), SIZEOF(SIZE_T)) From dbed370f61b126f268394ea9b42427910407e0cb Mon Sep 17 00:00:00 2001 From: Stefan Kieszkowski <85728496+stefankiesz@users.noreply.github.com> Date: Thu, 13 Nov 2025 13:55:36 -0800 Subject: [PATCH 03/17] Fix a couple bugs, add IP address to string helper function --- src/source/Ice/IceAgent.c | 2 +- src/source/Ice/Network.c | 10 ++++++++++ src/source/Ice/TurnConnection.c | 20 +++++++++++--------- src/source/Ice/TurnConnection.h | 4 ++-- src/source/Ice/TurnConnectionStateMachine.c | 6 +++--- src/source/Include_i.h | 2 ++ 6 files changed, 29 insertions(+), 15 deletions(-) diff --git a/src/source/Ice/IceAgent.c b/src/source/Ice/IceAgent.c index 565c233ac9..0704f21f7a 100644 --- a/src/source/Ice/IceAgent.c +++ b/src/source/Ice/IceAgent.c @@ -1964,7 +1964,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; diff --git a/src/source/Ice/Network.c b/src/source/Ice/Network.c index 930321a1d0..29207bcf87 100644 --- a/src/source/Ice/Network.c +++ b/src/source/Ice/Network.c @@ -506,6 +506,16 @@ STATUS getIpAddrStr(PKvsIpAddress pKvsIpAddress, PCHAR pBuffer, UINT32 bufferLen return retStatus; } +const PCHAR kvsIpAddressToString(PKvsIpAddress pAddr) +{ + static __thread char addrBuf[64]; + STATUS retStatus = getIpAddrStr(pAddr, addrBuf, sizeof(addrBuf)); + if (STATUS_FAILED(retStatus)) { + SNPRINTF(addrBuf, sizeof(addrBuf), ""); + } + return addrBuf; +} + BOOL isSameIpAddress(PKvsIpAddress pAddr1, PKvsIpAddress pAddr2, BOOL checkPort) { BOOL ret; diff --git a/src/source/Ice/TurnConnection.c b/src/source/Ice/TurnConnection.c index 1411bfd2e5..fead5d4094 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, @@ -827,7 +828,7 @@ STATUS turnConnectionSendData(PTurnConnection pTurnConnection, PBYTE pBuf, UINT3 putInt16((PINT16) (pTurnConnection->sendDataBuffer + 2), (UINT16) bufLen); MEMCPY(pTurnConnection->sendDataBuffer + TURN_DATA_CHANNEL_SEND_OVERHEAD, pBuf, bufLen); - getTurnConnectionIpAddress(pTurnConnection, pTurnServerIp); + getTurnConnectionIpAddress(pTurnConnection, &pTurnServerIp); retStatus = iceUtilsSendData(pTurnConnection->sendDataBuffer, paddedDataLen, pTurnServerIp, pTurnConnection->pControlChannel, NULL, FALSE); @@ -917,7 +918,7 @@ STATUS turnConnectionRefreshAllocation(PTurnConnection pTurnConnection) pStunAttributeLifetime->lifetime = DEFAULT_TURN_ALLOCATION_LIFETIME_SECONDS; - getTurnConnectionIpAddress(pTurnConnection, pTurnServerIp); + getTurnConnectionIpAddress(pTurnConnection, &pTurnServerIp); CHK_STATUS(iceUtilsSendStunPacket(pTurnConnection->pTurnAllocationRefreshPacket, pTurnConnection->longTermKey, ARRAY_SIZE(pTurnConnection->longTermKey), pTurnServerIp, @@ -1118,7 +1119,7 @@ STATUS checkTurnPeerConnections(PTurnConnection pTurnConnection) CHK(pTurnPeer->pTransactionIdStore != NULL, STATUS_INVALID_OPERATION); transactionIdStoreInsert(pTurnPeer->pTransactionIdStore, pTurnConnection->pTurnCreatePermissionPacket->header.transactionId); - getTurnConnectionIpAddress(pTurnConnection, pTurnServerIp); + getTurnConnectionIpAddress(pTurnConnection, &pTurnServerIp); sendStatus = iceUtilsSendStunPacket(pTurnConnection->pTurnCreatePermissionPacket, pTurnConnection->longTermKey, ARRAY_SIZE(pTurnConnection->longTermKey), pTurnServerIp, pTurnConnection->pControlChannel, NULL, FALSE); @@ -1145,7 +1146,7 @@ STATUS checkTurnPeerConnections(PTurnConnection pTurnConnection) CHK(pTurnPeer->pTransactionIdStore != NULL, STATUS_INVALID_OPERATION); transactionIdStoreInsert(pTurnPeer->pTransactionIdStore, pTurnConnection->pTurnChannelBindPacket->header.transactionId); - getTurnConnectionIpAddress(pTurnConnection, pTurnServerIp); + getTurnConnectionIpAddress(pTurnConnection, &pTurnServerIp); sendStatus = iceUtilsSendStunPacket(pTurnConnection->pTurnChannelBindPacket, pTurnConnection->longTermKey, ARRAY_SIZE(pTurnConnection->longTermKey), pTurnServerIp, pTurnConnection->pControlChannel, NULL, FALSE); @@ -1295,16 +1296,17 @@ VOID turnConnectionFatalError(PTurnConnection pTurnConnection, STATUS errorStatu pTurnConnection->state = TURN_STATE_FAILED; } -STATUS getTurnConnectionIpAddress(PTurnConnection pTurnConnection, PKvsIpAddress pTurnConnectionIp) +STATUS getTurnConnectionIpAddress(PTurnConnection pTurnConnection, PKvsIpAddress* ppTurnConnectionIpAddress) { STATUS retStatus = STATUS_SUCCESS; - CHK(pTurnConnection != NULL && pTurnConnectionIp != NULL, STATUS_NULL_ARG); + 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) { - *pTurnConnectionIp = pTurnConnection->turnServer.ipAddresses.ipv4Address; + *ppTurnConnectionIpAddress = &pTurnConnection->turnServer.ipAddresses.ipv4Address; } else { - *pTurnConnectionIp = pTurnConnection->turnServer.ipAddresses.ipv6Address; + *ppTurnConnectionIpAddress = &pTurnConnection->turnServer.ipAddresses.ipv6Address; } CleanUp: CHK_LOG_ERR(retStatus); diff --git a/src/source/Ice/TurnConnection.h b/src/source/Ice/TurnConnection.h index cc07d55a2c..816ab52bb4 100644 --- a/src/source/Ice/TurnConnection.h +++ b/src/source/Ice/TurnConnection.h @@ -189,7 +189,7 @@ struct __TurnConnection { 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); @@ -220,7 +220,7 @@ VOID turnConnectionFatalError(PTurnConnection, STATUS); PTurnPeer turnConnectionGetPeerWithChannelNumber(PTurnConnection, UINT16); PTurnPeer turnConnectionGetPeerWithIp(PTurnConnection, PKvsIpAddress); -STATUS getTurnConnectionIpAddress(PTurnConnection, PKvsIpAddress); +STATUS getTurnConnectionIpAddress(PTurnConnection, PKvsIpAddress*); STATUS checkTurnPeerConnections(PTurnConnection); diff --git a/src/source/Ice/TurnConnectionStateMachine.c b/src/source/Ice/TurnConnectionStateMachine.c index 5e66a46b90..7426f053de 100644 --- a/src/source/Ice/TurnConnectionStateMachine.c +++ b/src/source/Ice/TurnConnectionStateMachine.c @@ -280,7 +280,7 @@ STATUS executeGetCredentialsTurnState(UINT64 customData, UINT64 time) CHK(currentTime <= pTurnConnection->stateTimeoutTime, STATUS_TURN_CONNECTION_GET_CREDENTIALS_FAILED); } - getTurnConnectionIpAddress(pTurnConnection, pTurnServerIp); + CHK_STATUS(getTurnConnectionIpAddress(pTurnConnection, &pTurnServerIp)); CHK_STATUS(iceUtilsSendStunPacket(pTurnConnection->pTurnPacket, NULL, 0, pTurnServerIp, pTurnConnection->pControlChannel, NULL, FALSE)); @@ -353,7 +353,7 @@ STATUS executeAllocationTurnState(UINT64 customData, UINT64 time) CHK(currentTime <= pTurnConnection->stateTimeoutTime, STATUS_TURN_CONNECTION_ALLOCATION_FAILED); } - getTurnConnectionIpAddress(pTurnConnection, pTurnServerIp); + getTurnConnectionIpAddress(pTurnConnection, &pTurnServerIp); CHK_STATUS(iceUtilsSendStunPacket(pTurnConnection->pTurnPacket, pTurnConnection->longTermKey, ARRAY_SIZE(pTurnConnection->longTermKey), pTurnServerIp, pTurnConnection->pControlChannel, NULL, FALSE)); @@ -686,7 +686,7 @@ STATUS executeCleanUpTurnState(UINT64 customData, UINT64 time) (PStunAttributeHeader*) &pStunAttributeLifetime)); CHK(pStunAttributeLifetime != NULL, STATUS_INTERNAL_ERROR); pStunAttributeLifetime->lifetime = 0; - getTurnConnectionIpAddress(pTurnConnection, pTurnServerIp); + getTurnConnectionIpAddress(pTurnConnection, &pTurnServerIp); CHK_STATUS(iceUtilsSendStunPacket(pTurnConnection->pTurnAllocationRefreshPacket, pTurnConnection->longTermKey, ARRAY_SIZE(pTurnConnection->longTermKey), pTurnServerIp, pTurnConnection->pControlChannel, NULL, FALSE)); diff --git a/src/source/Include_i.h b/src/source/Include_i.h index b868d72339..d40550cf47 100644 --- a/src/source/Include_i.h +++ b/src/source/Include_i.h @@ -115,6 +115,7 @@ typedef struct { KvsIpAddress ipv6Address; } DualKvsIpAddresses, *PDualKvsIpAddresses; +#include static inline BOOL isIpv4Address(const KvsIpAddress* pAddress) { @@ -132,6 +133,7 @@ static inline BOOL isIpv6Address(const KvsIpAddress* pAddress) typedef STATUS (*IceServerSetIpFunc)(UINT64, PCHAR, PDualKvsIpAddresses); STATUS getIpAddrStr(PKvsIpAddress pKvsIpAddress, PCHAR pBuffer, UINT32 bufferLen); +const PCHAR kvsIpAddressToString(const KvsIpAddress* pAddr); //////////////////////////////////////////////////// // Project forward declarations From 35b5b74bdb7b7b20d07504179e470cea907208ce Mon Sep 17 00:00:00 2001 From: Stefan Kieszkowski <85728496+stefankiesz@users.noreply.github.com> Date: Fri, 21 Nov 2025 09:26:50 -0800 Subject: [PATCH 04/17] Fix datatype --- src/source/Include_i.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/source/Include_i.h b/src/source/Include_i.h index d40550cf47..91f3847bb3 100644 --- a/src/source/Include_i.h +++ b/src/source/Include_i.h @@ -133,7 +133,7 @@ static inline BOOL isIpv6Address(const KvsIpAddress* pAddress) typedef STATUS (*IceServerSetIpFunc)(UINT64, PCHAR, PDualKvsIpAddresses); STATUS getIpAddrStr(PKvsIpAddress pKvsIpAddress, PCHAR pBuffer, UINT32 bufferLen); -const PCHAR kvsIpAddressToString(const KvsIpAddress* pAddr); +const PCHAR kvsIpAddressToString(const PKvsIpAddress pAddr); //////////////////////////////////////////////////// // Project forward declarations From a769d51756fd6baff3ab1aa1f232cec82a40dd4f Mon Sep 17 00:00:00 2001 From: Stefan Kieszkowski <85728496+stefankiesz@users.noreply.github.com> Date: Fri, 21 Nov 2025 09:52:28 -0800 Subject: [PATCH 05/17] Address comments --- samples/Common.c | 2 +- src/source/Ice/IceAgent.c | 14 +++++++------- src/source/Ice/IceUtils.c | 6 ++---- src/source/Ice/Network.c | 15 +++------------ src/source/Ice/SocketConnection.c | 2 +- src/source/Ice/TurnConnection.c | 2 ++ src/source/Include_i.h | 5 ++--- src/source/Stun/Stun.c | 8 ++++---- 8 files changed, 22 insertions(+), 32 deletions(-) diff --git a/samples/Common.c b/samples/Common.c index a3c66c0060..a3e635fb1d 100644 --- a/samples/Common.c +++ b/samples/Common.c @@ -922,7 +922,7 @@ STATUS createSampleConfiguration(PCHAR channelName, SIGNALING_CHANNEL_ROLE_TYPE pSampleConfiguration->channelInfo.pTags = NULL; pSampleConfiguration->channelInfo.channelType = SIGNALING_CHANNEL_TYPE_SINGLE_MASTER; pSampleConfiguration->channelInfo.channelRoleType = roleType; - pSampleConfiguration->channelInfo.cachingPolicy = SIGNALING_API_CALL_CACHE_TYPE_NONE; + pSampleConfiguration->channelInfo.cachingPolicy = SIGNALING_API_CALL_CACHE_TYPE_FILE; pSampleConfiguration->channelInfo.cachingPeriod = SIGNALING_API_CALL_CACHE_TTL_SENTINEL_VALUE; pSampleConfiguration->channelInfo.asyncIceServerConfig = TRUE; // has no effect pSampleConfiguration->channelInfo.retry = TRUE; diff --git a/src/source/Ice/IceAgent.c b/src/source/Ice/IceAgent.c index 0704f21f7a..1e686d55d7 100644 --- a/src/source/Ice/IceAgent.c +++ b/src/source/Ice/IceAgent.c @@ -1021,7 +1021,7 @@ STATUS findCandidateWithIp(PKvsIpAddress pIpAddress, PDoubleList pCandidateList, pIceCandidate = (PIceCandidate) data; pCurNode = pCurNode->pNext; - addrLen = isIpv4Address(pIpAddress) ? IPV4_ADDRESS_LENGTH : IPV6_ADDRESS_LENGTH; + addrLen = IS_IPV4_ADDR(pIpAddress) ? IPV4_ADDRESS_LENGTH : IPV6_ADDRESS_LENGTH; if (pIpAddress->family == pIceCandidate->ipAddress.family && MEMCMP(pIceCandidate->ipAddress.address, pIpAddress->address, addrLen) == 0 && pIpAddress->port == pIceCandidate->ipAddress.port) { pTargetIceCandidate = pIceCandidate; @@ -1240,7 +1240,7 @@ STATUS findIceCandidatePairWithLocalSocketConnectionAndRemoteAddr(PIceAgent pIce CHK(pIceAgent != NULL && ppIceCandidatePair != NULL && pSocketConnection != NULL, STATUS_NULL_ARG); - addrLen = isIpv4Address(pRemoteAddr) ? IPV4_ADDRESS_LENGTH : IPV6_ADDRESS_LENGTH; + addrLen = IS_IPV4_ADDR(pRemoteAddr) ? IPV4_ADDRESS_LENGTH : IPV6_ADDRESS_LENGTH; CHK_STATUS(doubleListGetHeadNode(pIceAgent->iceCandidatePairs, &pCurNode)); while (pCurNode != NULL && pTargetIceCandidatePair == NULL) { @@ -1815,7 +1815,7 @@ STATUS iceAgentInitSrflxCandidate(PIceAgent pIceAgent) // Create and start the connection listener outside of the locks for (j = 0; j < srflxCount; j++) { pCandidate = srflxCandidates[j]; - if (isIpv4Address(&(pCandidate->ipAddress))) { + if (IS_IPV4_ADDR(&(pCandidate->ipAddress))) { DLOGI("Initializing an IPv4 STUN candidate..."); } else { DLOGI("Initializing an IPv6 STUN candidate..."); @@ -1984,11 +1984,11 @@ STATUS iceAgentInitRelayCandidate(PIceAgent pIceAgent, UINT32 iceServerIndex, KV // Add only peers with matching IP family to the TURN connection. if (turnServerIpFamily == KVS_IP_FAMILY_TYPE_IPV4) { - if (isIpv4Address(&pCandidate->ipAddress)) { + if (IS_IPV4_ADDR(&pCandidate->ipAddress)) { CHK_STATUS(turnConnectionAddPeer(pTurnConnection, &pCandidate->ipAddress)); } } else { - if (isIpv6Address(&pCandidate->ipAddress)) { + if (IS_IPV6_ADDR(&pCandidate->ipAddress)) { CHK_STATUS(turnConnectionAddPeer(pTurnConnection, &pCandidate->ipAddress)); } @@ -2476,7 +2476,7 @@ STATUS incomingDataHandler(UINT64 customData, PSocketConnection pSocketConnectio MUTEX_LOCK(pIceAgent->lock); locked = TRUE; - addrLen = isIpv4Address(pSrc) ? IPV4_ADDRESS_LENGTH : IPV6_ADDRESS_LENGTH; + addrLen = IS_IPV4_ADDR(pSrc) ? IPV4_ADDRESS_LENGTH : IPV6_ADDRESS_LENGTH; if (pIceAgent->pDataSendingIceCandidatePair != NULL && pIceAgent->pDataSendingIceCandidatePair->local->pSocketConnection == pSocketConnection && pIceAgent->pDataSendingIceCandidatePair->remote->ipAddress.family == pSrc->family && @@ -2514,7 +2514,7 @@ STATUS iceCandidateSerialize(PIceCandidate pIceCandidate, PCHAR pOutputData, PUI CHK(pIceCandidate != NULL && pOutputLength != NULL, STATUS_NULL_ARG); // TODO FIXME real source of randomness - if (isIpv4Address(&(pIceCandidate->ipAddress))) { + if (IS_IPV4_ADDR(&(pIceCandidate->ipAddress))) { amountWritten = SNPRINTF(pOutputData, pOutputData == NULL ? 0 : *pOutputLength, "%u 1 udp %u %d.%d.%d.%d %d typ %s raddr 0.0.0.0 rport 0 generation 0 network-cost 999", pIceCandidate->foundation, pIceCandidate->priority, pIceCandidate->ipAddress.address[0], pIceCandidate->ipAddress.address[1], diff --git a/src/source/Ice/IceUtils.c b/src/source/Ice/IceUtils.c index 9209639aec..3c84664a42 100644 --- a/src/source/Ice/IceUtils.c +++ b/src/source/Ice/IceUtils.c @@ -283,15 +283,13 @@ STATUS parseIceServer(PIceServer pIceServer, PCHAR url, PCHAR username, PCHAR cr 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))); - DLOGP("Resolved ICE Server IPv4 address for %s: %s", pIceServer->url, addressResolvedIPv4); - DLOGP("...with port: %u", pIceServer->ipAddresses.ipv4Address.port); + 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))); - DLOGP("Resolved ICE Server IPv6 address for %s: %s", pIceServer->url, addressResolvedIPv6); - DLOGP("...with port: %u", pIceServer->ipAddresses.ipv6Address.port); + 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/Network.c b/src/source/Ice/Network.c index 29207bcf87..0bb6f98765 100644 --- a/src/source/Ice/Network.c +++ b/src/source/Ice/Network.c @@ -269,7 +269,7 @@ STATUS socketBind(PKvsIpAddress pHostIpAddress, INT32 sockfd) if (bind(sockfd, sockAddr, addrLen) < 0) { CHK_STATUS(getIpAddrStr(pHostIpAddress, ipAddrStr, ARRAY_SIZE(ipAddrStr))); - DLOGW("bind() failed for ip%s address: %s, port %u with errno %s", isIpv4Address(pHostIpAddress) ? EMPTY_STRING : "V6", ipAddrStr, + DLOGW("bind() failed for ip%s address: %s, port %u with errno %s", IS_IPV4_ADDR(pHostIpAddress) ? EMPTY_STRING : "V6", ipAddrStr, (UINT16) getInt16(pHostIpAddress->port), getErrorString(getErrorCode())); CHK(FALSE, STATUS_BINDING_SOCKET_FAILED); } @@ -487,7 +487,7 @@ STATUS getIpAddrStr(PKvsIpAddress pKvsIpAddress, PCHAR pBuffer, UINT32 bufferLen CHK(pKvsIpAddress != NULL, STATUS_NULL_ARG); CHK(pBuffer != NULL && bufferLen > 0, STATUS_INVALID_ARG); - if (isIpv4Address(pKvsIpAddress)) { + if (IS_IPV4_ADDR(pKvsIpAddress)) { generatedStrLen = SNPRINTF(pBuffer, bufferLen, "%u.%u.%u.%u", pKvsIpAddress->address[0], pKvsIpAddress->address[1], pKvsIpAddress->address[2], pKvsIpAddress->address[3]); } else { @@ -506,15 +506,6 @@ STATUS getIpAddrStr(PKvsIpAddress pKvsIpAddress, PCHAR pBuffer, UINT32 bufferLen return retStatus; } -const PCHAR kvsIpAddressToString(PKvsIpAddress pAddr) -{ - static __thread char addrBuf[64]; - STATUS retStatus = getIpAddrStr(pAddr, addrBuf, sizeof(addrBuf)); - if (STATUS_FAILED(retStatus)) { - SNPRINTF(addrBuf, sizeof(addrBuf), ""); - } - return addrBuf; -} BOOL isSameIpAddress(PKvsIpAddress pAddr1, PKvsIpAddress pAddr2, BOOL checkPort) { @@ -525,7 +516,7 @@ BOOL isSameIpAddress(PKvsIpAddress pAddr1, PKvsIpAddress pAddr2, BOOL checkPort) return FALSE; } - addrLen = isIpv4Address(pAddr1) ? IPV4_ADDRESS_LENGTH : IPV6_ADDRESS_LENGTH; + addrLen = IS_IPV4_ADDR(pAddr1) ? IPV4_ADDRESS_LENGTH : IPV6_ADDRESS_LENGTH; ret = (pAddr1->family == pAddr2->family && MEMCMP(pAddr1->address, pAddr2->address, addrLen) == 0 && (!checkPort || pAddr1->port == pAddr2->port)); diff --git a/src/source/Ice/SocketConnection.c b/src/source/Ice/SocketConnection.c index cf3cf61d3f..af8305ef79 100644 --- a/src/source/Ice/SocketConnection.c +++ b/src/source/Ice/SocketConnection.c @@ -379,7 +379,7 @@ STATUS socketSendDataWithRetry(PSocketConnection pSocketConnection, PBYTE buf, U CHK(buf != NULL && bufLen > 0, STATUS_INVALID_ARG); if (pDestIp != NULL) { - if (isIpv4Address(pDestIp)) { + if (IS_IPV4_ADDR(pDestIp)) { addrLen = SIZEOF(ipv4Addr); MEMSET(&ipv4Addr, 0x00, SIZEOF(ipv4Addr)); ipv4Addr.sin_family = AF_INET; diff --git a/src/source/Ice/TurnConnection.c b/src/source/Ice/TurnConnection.c index fead5d4094..84fab21ce3 100644 --- a/src/source/Ice/TurnConnection.c +++ b/src/source/Ice/TurnConnection.c @@ -1298,6 +1298,7 @@ VOID turnConnectionFatalError(PTurnConnection pTurnConnection, STATUS errorStatu STATUS getTurnConnectionIpAddress(PTurnConnection pTurnConnection, PKvsIpAddress* ppTurnConnectionIpAddress) { + ENTERS(); STATUS retStatus = STATUS_SUCCESS; CHK_ERR(ppTurnConnectionIpAddress != NULL, STATUS_NULL_ARG, "ppTurnConnectionIp is NULL"); @@ -1310,5 +1311,6 @@ STATUS getTurnConnectionIpAddress(PTurnConnection pTurnConnection, PKvsIpAddress } CleanUp: CHK_LOG_ERR(retStatus); + LEAVES(); return retStatus; } diff --git a/src/source/Include_i.h b/src/source/Include_i.h index 91f3847bb3..7fe063eb88 100644 --- a/src/source/Include_i.h +++ b/src/source/Include_i.h @@ -117,12 +117,12 @@ typedef struct { #include -static inline BOOL isIpv4Address(const KvsIpAddress* pAddress) +static inline BOOL IS_IPV4_ADDR(const KvsIpAddress* pAddress) { return pAddress != NULL && pAddress->family == KVS_IP_FAMILY_TYPE_IPV4; } -static inline BOOL isIpv6Address(const KvsIpAddress* pAddress) +static inline BOOL IS_IPV6_ADDR(const KvsIpAddress* pAddress) { return pAddress != NULL && pAddress->family == KVS_IP_FAMILY_TYPE_IPV6; } @@ -133,7 +133,6 @@ static inline BOOL isIpv6Address(const KvsIpAddress* pAddress) typedef STATUS (*IceServerSetIpFunc)(UINT64, PCHAR, PDualKvsIpAddresses); STATUS getIpAddrStr(PKvsIpAddress pKvsIpAddress, PCHAR pBuffer, UINT32 bufferLen); -const PCHAR kvsIpAddressToString(const PKvsIpAddress pAddr); //////////////////////////////////////////////////// // Project forward declarations diff --git a/src/source/Stun/Stun.c b/src/source/Stun/Stun.c index 4dd7c8c6e1..3414061a45 100644 --- a/src/source/Stun/Stun.c +++ b/src/source/Stun/Stun.c @@ -25,7 +25,7 @@ STATUS stunPackageIpAddr(PStunHeader pStunHeader, STUN_ATTRIBUTE_TYPE type, PKvs * - 4 byte or 16 byte ip address */ dataLen += STUN_ATTRIBUTE_HEADER_LEN + STUN_ATTRIBUTE_ADDRESS_HEADER_LEN; - dataLen += isIpv4Address(pIndirected) ? IPV4_ADDRESS_LENGTH : IPV6_ADDRESS_LENGTH; + dataLen += IS_IPV4_ADDR(pIndirected) ? IPV4_ADDRESS_LENGTH : IPV6_ADDRESS_LENGTH; // Check if we are asked for size only and early return if so CHK(pBuffer != NULL, STATUS_SUCCESS); @@ -55,7 +55,7 @@ STATUS stunPackageIpAddr(PStunHeader pStunHeader, STUN_ATTRIBUTE_TYPE type, PKvs MEMCPY(pCurrentBufferPosition, (PBYTE) &pIndirected->port, SIZEOF(pIndirected->port)); pCurrentBufferPosition += SIZEOF(pIndirected->port); - MEMCPY(pCurrentBufferPosition, pIndirected->address, isIpv4Address(pIndirected) ? IPV4_ADDRESS_LENGTH : IPV6_ADDRESS_LENGTH); + MEMCPY(pCurrentBufferPosition, pIndirected->address, IS_IPV4_ADDR(pIndirected) ? IPV4_ADDRESS_LENGTH : IPV6_ADDRESS_LENGTH); CleanUp: @@ -1163,7 +1163,7 @@ STATUS appendStunAddressAttribute(PStunPacket pStunPacket, STUN_ATTRIBUTE_TYPE a // Set up the new entry and copy data over pStunPacket->attributeList[pStunPacket->attributesCount++] = (PStunAttributeHeader) pAttribute; - pAttribute->attribute.length = STUN_ATTRIBUTE_ADDRESS_HEADER_LEN + (isIpv4Address(pAddress) ? IPV4_ADDRESS_LENGTH : IPV6_ADDRESS_LENGTH); + pAttribute->attribute.length = STUN_ATTRIBUTE_ADDRESS_HEADER_LEN + (IS_IPV4_ADDR(pAddress) ? IPV4_ADDRESS_LENGTH : IPV6_ADDRESS_LENGTH); pAttribute->attribute.type = addressAttributeType; // Copy the attribute entirely @@ -1256,7 +1256,7 @@ STATUS xorIpAddress(PKvsIpAddress pAddress, PBYTE pTransactionId) UINT32 i; CHK(pAddress != NULL, STATUS_NULL_ARG); - CHK(isIpv4Address(pAddress) || pTransactionId != NULL, STATUS_INVALID_ARG); + CHK(IS_IPV4_ADDR(pAddress) || pTransactionId != NULL, STATUS_INVALID_ARG); // Perform the XOR-ing pAddress->port = (UINT16) (getInt16(STUN_HEADER_MAGIC_COOKIE >> 16)) ^ pAddress->port; From f507e5e0f0a6f419326e15b743e173903b566a6b Mon Sep 17 00:00:00 2001 From: Stefan Kieszkowski <85728496+stefankiesz@users.noreply.github.com> Date: Fri, 21 Nov 2025 10:19:43 -0800 Subject: [PATCH 06/17] Merge in dual-stack control-plane endpoint changes --- .../libkvsCommonLws-CMakeLists.txt | 2 +- .../Dependencies/libwebsockets-CMakeLists.txt | 2 +- samples/Common.c | 33 +++- samples/Samples.h | 13 +- samples/kvsWebRTCClientMaster.c | 10 +- samples/kvsWebRTCClientViewer.c | 10 +- samples/kvsWebRTCClientViewerGstSample.c | 10 +- samples/kvsWebrtcClientMasterGstSample.c | 10 +- .../kinesis/video/webrtcclient/Include.h | 5 + src/source/Ice/Network.c | 2 +- src/source/Signaling/ChannelInfo.c | 4 +- src/source/Signaling/ChannelInfo.h | 3 - tst/DualStackEndpointsTest.cpp | 143 ++++++++++++++++++ 13 files changed, 226 insertions(+), 21 deletions(-) create mode 100644 tst/DualStackEndpointsTest.cpp diff --git a/CMake/Dependencies/libkvsCommonLws-CMakeLists.txt b/CMake/Dependencies/libkvsCommonLws-CMakeLists.txt index 5cd8f1df67..3ba4f9646d 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 dual-stack-support 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 d0a756eb1d..e45210843c 100644 --- a/CMake/Dependencies/libwebsockets-CMakeLists.txt +++ b/CMake/Dependencies/libwebsockets-CMakeLists.txt @@ -63,7 +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_IPV6=1 + -DLWS_WITH_IPV6=ON BUILD_ALWAYS TRUE TEST_COMMAND "" ) diff --git a/samples/Common.c b/samples/Common.c index a3e635fb1d..83d7907000 100644 --- a/samples/Common.c +++ b/samples/Common.c @@ -829,14 +829,13 @@ STATUS lookForSslCert(PSampleConfiguration* ppSampleConfiguration) return retStatus; } -STATUS createSampleConfiguration(PCHAR channelName, SIGNALING_CHANNEL_ROLE_TYPE roleType, BOOL trickleIce, BOOL useTurn, UINT32 logLevel, - PSampleConfiguration* ppSampleConfiguration) +STATUS createSampleConfiguration(PCreateSampleConfigurationParams pCreateSampleConfigurationParams, PSampleConfiguration* ppSampleConfiguration) { STATUS retStatus = STATUS_SUCCESS; PCHAR pAccessKey, pSecretKey, pSessionToken; PSampleConfiguration pSampleConfiguration = NULL; - CHK(ppSampleConfiguration != NULL, STATUS_NULL_ARG); + CHK(ppSampleConfiguration != NULL && pCreateSampleConfigurationParams != NULL, STATUS_NULL_ARG); CHK(NULL != (pSampleConfiguration = (PSampleConfiguration) MEMCALLOC(1, SIZEOF(SampleConfiguration))), STATUS_NOT_ENOUGH_MEMORY); @@ -905,23 +904,41 @@ STATUS createSampleConfiguration(PCHAR channelName, SIGNALING_CHANNEL_ROLE_TYPE pSampleConfiguration->signalingSendMessageLock = MUTEX_CREATE(FALSE); /* This is ignored for master. Master can extract the info from offer. Viewer has to know if peer can trickle or * not ahead of time. */ - pSampleConfiguration->trickleIce = trickleIce; - pSampleConfiguration->useTurn = useTurn; + pSampleConfiguration->trickleIce = pCreateSampleConfigurationParams->trickleIce; + pSampleConfiguration->useTurn = pCreateSampleConfigurationParams->useTurn; pSampleConfiguration->enableSendingMetricsToViewerViaDc = FALSE; pSampleConfiguration->receiveAudioVideoSource = NULL; pSampleConfiguration->channelInfo.version = CHANNEL_INFO_CURRENT_VERSION; - pSampleConfiguration->channelInfo.pChannelName = channelName; + pSampleConfiguration->channelInfo.pChannelName = pCreateSampleConfigurationParams->channelName; #ifdef IOT_CORE_ENABLE_CREDENTIALS if ((pIotCoreCertificateId = GETENV(IOT_CORE_CERTIFICATE_ID)) != NULL) { pSampleConfiguration->channelInfo.pChannelName = pIotCoreCertificateId; } #endif + + if (pCreateSampleConfigurationParams->useDualStackEndpoints) { + // Create the custom fully qualified control plane endpoint, sans the legacy/dual-stack postfix. + SNPRINTF(pSampleConfiguration->customControlPlaneEndpoint, MAX_CONTROL_PLANE_URI_CHAR_LEN, "%s%s.%s", CONTROL_PLANE_URI_PREFIX, + KINESIS_VIDEO_SERVICE_NAME, pSampleConfiguration->channelInfo.pRegion); + + if (STRSTR(pSampleConfiguration->channelInfo.pRegion, "cn-")) { + STRCAT(pSampleConfiguration->customControlPlaneEndpoint, + CONTROL_PLANE_URI_POSTFIX_CN_DUAL_STACK); // Will use CN region dual-stack endpoint. + } else { + STRCAT(pSampleConfiguration->customControlPlaneEndpoint, CONTROL_PLANE_URI_POSTFIX_DUAL_STACK); // Will use Dual-stack endpoint. + } + + pSampleConfiguration->channelInfo.pControlPlaneUrl = pSampleConfiguration->customControlPlaneEndpoint; + } else { + pSampleConfiguration->channelInfo.pControlPlaneUrl = NULL; // Will use default legacy endpoints. + } + pSampleConfiguration->channelInfo.pKmsKeyId = NULL; pSampleConfiguration->channelInfo.tagCount = 0; pSampleConfiguration->channelInfo.pTags = NULL; pSampleConfiguration->channelInfo.channelType = SIGNALING_CHANNEL_TYPE_SINGLE_MASTER; - pSampleConfiguration->channelInfo.channelRoleType = roleType; + pSampleConfiguration->channelInfo.channelRoleType = pCreateSampleConfigurationParams->roleType; pSampleConfiguration->channelInfo.cachingPolicy = SIGNALING_API_CALL_CACHE_TYPE_FILE; pSampleConfiguration->channelInfo.cachingPeriod = SIGNALING_API_CALL_CACHE_TTL_SENTINEL_VALUE; pSampleConfiguration->channelInfo.asyncIceServerConfig = TRUE; // has no effect @@ -936,7 +953,7 @@ STATUS createSampleConfiguration(PCHAR channelName, SIGNALING_CHANNEL_ROLE_TYPE pSampleConfiguration->signalingClientCallbacks.customData = (UINT64) pSampleConfiguration; pSampleConfiguration->clientInfo.version = SIGNALING_CLIENT_INFO_CURRENT_VERSION; - pSampleConfiguration->clientInfo.loggingLevel = logLevel; + pSampleConfiguration->clientInfo.loggingLevel = pCreateSampleConfigurationParams->logLevel; pSampleConfiguration->clientInfo.cacheFilePath = NULL; // Use the default path pSampleConfiguration->clientInfo.signalingClientCreationMaxRetryAttempts = CREATE_SIGNALING_CLIENT_RETRY_ATTEMPTS_SENTINEL_VALUE; pSampleConfiguration->iceCandidatePairStatsTimerId = MAX_UINT32; diff --git a/samples/Samples.h b/samples/Samples.h index 2e1324d310..ff224b0df1 100644 --- a/samples/Samples.h +++ b/samples/Samples.h @@ -115,6 +115,15 @@ typedef struct { UINT64 prevTs; } RtcMetricsHistory, *PRtcMetricsHistory; +typedef struct { + PCHAR channelName; + SIGNALING_CHANNEL_ROLE_TYPE roleType; + BOOL trickleIce; + BOOL useTurn; + BOOL useDualStackEndpoints; + UINT32 logLevel; +} CreateSampleConfigurationParams, *PCreateSampleConfigurationParams; + typedef struct { volatile ATOMIC_BOOL appTerminateFlag; volatile ATOMIC_BOOL interrupted; @@ -176,6 +185,8 @@ typedef struct { UINT32 logLevel; BOOL enableTwcc; BOOL enableIceStats; + BOOL useDualStackEndpoints; + CHAR customControlPlaneEndpoint[MAX_CONTROL_PLANE_URI_CHAR_LEN]; } SampleConfiguration, *PSampleConfiguration; typedef struct { @@ -247,7 +258,7 @@ PVOID sampleReceiveAudioVideoFrame(PVOID); PVOID getPeriodicIceCandidatePairStats(PVOID); STATUS getIceCandidatePairStatsCallback(UINT32, UINT64, UINT64); STATUS pregenerateCertTimerCallback(UINT32, UINT64, UINT64); -STATUS createSampleConfiguration(PCHAR, SIGNALING_CHANNEL_ROLE_TYPE, BOOL, BOOL, UINT32, PSampleConfiguration*); +STATUS createSampleConfiguration(PCreateSampleConfigurationParams, PSampleConfiguration*); STATUS freeSampleConfiguration(PSampleConfiguration*); STATUS signalingClientStateChanged(UINT64, SIGNALING_CLIENT_STATE); STATUS signalingMessageReceived(UINT64, PReceivedSignalingMessage); diff --git a/samples/kvsWebRTCClientMaster.c b/samples/kvsWebRTCClientMaster.c index 5123137b08..f4dffe10be 100644 --- a/samples/kvsWebRTCClientMaster.c +++ b/samples/kvsWebRTCClientMaster.c @@ -12,6 +12,7 @@ INT32 main(INT32 argc, CHAR* argv[]) signalingClientMetrics.version = SIGNALING_CLIENT_METRICS_CURRENT_VERSION; RTC_CODEC audioCodec = RTC_CODEC_OPUS; RTC_CODEC videoCodec = RTC_CODEC_H264_PROFILE_42E01F_LEVEL_ASYMMETRY_ALLOWED_PACKETIZATION_MODE; + CreateSampleConfigurationParams createSampleConfigurationParams; SET_INSTRUMENTED_ALLOCATORS(); UINT32 logLevel = setLogLevel(); @@ -27,7 +28,14 @@ INT32 main(INT32 argc, CHAR* argv[]) pChannelName = argc > 1 ? argv[1] : SAMPLE_CHANNEL_NAME; #endif - CHK_STATUS(createSampleConfiguration(pChannelName, SIGNALING_CHANNEL_ROLE_TYPE_MASTER, TRUE, TRUE, logLevel, &pSampleConfiguration)); + createSampleConfigurationParams.channelName = pChannelName; + createSampleConfigurationParams.roleType = SIGNALING_CHANNEL_ROLE_TYPE_MASTER; + createSampleConfigurationParams.trickleIce = TRUE; + createSampleConfigurationParams.useTurn = TRUE; + createSampleConfigurationParams.useDualStackEndpoints = FALSE; + createSampleConfigurationParams.logLevel = logLevel; + + CHK_STATUS(createSampleConfiguration(&createSampleConfigurationParams, &pSampleConfiguration)); if (argc > 3) { if (!STRCMP(argv[3], AUDIO_CODEC_NAME_OPUS)) { diff --git a/samples/kvsWebRTCClientViewer.c b/samples/kvsWebRTCClientViewer.c index a4730d06b1..da5749ff74 100644 --- a/samples/kvsWebRTCClientViewer.c +++ b/samples/kvsWebRTCClientViewer.c @@ -44,6 +44,7 @@ INT32 main(INT32 argc, CHAR* argv[]) BOOL locked = FALSE; PCHAR pChannelName; CHAR clientId[256]; + CreateSampleConfigurationParams createSampleConfigurationParams; SET_INSTRUMENTED_ALLOCATORS(); UINT32 logLevel = setLogLevel(); @@ -81,7 +82,14 @@ INT32 main(INT32 argc, CHAR* argv[]) } } - CHK_STATUS(createSampleConfiguration(pChannelName, SIGNALING_CHANNEL_ROLE_TYPE_VIEWER, TRUE, TRUE, logLevel, &pSampleConfiguration)); + createSampleConfigurationParams.channelName = pChannelName; + createSampleConfigurationParams.roleType = SIGNALING_CHANNEL_ROLE_TYPE_VIEWER; + createSampleConfigurationParams.trickleIce = TRUE; + createSampleConfigurationParams.useTurn = TRUE; + createSampleConfigurationParams.useDualStackEndpoints = FALSE; + createSampleConfigurationParams.logLevel = logLevel; + + CHK_STATUS(createSampleConfiguration(&createSampleConfigurationParams, &pSampleConfiguration)); pSampleConfiguration->mediaType = SAMPLE_STREAMING_AUDIO_VIDEO; pSampleConfiguration->audioCodec = audioCodec; pSampleConfiguration->videoCodec = videoCodec; diff --git a/samples/kvsWebRTCClientViewerGstSample.c b/samples/kvsWebRTCClientViewerGstSample.c index d31676d1db..160e323eb6 100644 --- a/samples/kvsWebRTCClientViewerGstSample.c +++ b/samples/kvsWebRTCClientViewerGstSample.c @@ -44,6 +44,7 @@ INT32 main(INT32 argc, CHAR* argv[]) BOOL locked = FALSE; PCHAR pChannelName; CHAR clientId[256]; + CreateSampleConfigurationParams createSampleConfigurationParams; SET_INSTRUMENTED_ALLOCATORS(); UINT32 logLevel = setLogLevel(); @@ -75,7 +76,14 @@ INT32 main(INT32 argc, CHAR* argv[]) } } - CHK_STATUS(createSampleConfiguration(pChannelName, SIGNALING_CHANNEL_ROLE_TYPE_VIEWER, TRUE, TRUE, logLevel, &pSampleConfiguration)); + createSampleConfigurationParams.channelName = pChannelName; + createSampleConfigurationParams.roleType = SIGNALING_CHANNEL_ROLE_TYPE_VIEWER; + createSampleConfigurationParams.trickleIce = TRUE; + createSampleConfigurationParams.useTurn = TRUE; + createSampleConfigurationParams.useDualStackEndpoints = FALSE; + createSampleConfigurationParams.logLevel = logLevel; + + CHK_STATUS(createSampleConfiguration(&createSampleConfigurationParams, &pSampleConfiguration)); pSampleConfiguration->mediaType = SAMPLE_STREAMING_AUDIO_VIDEO; pSampleConfiguration->receiveAudioVideoSource = receiveGstreamerAudioVideo; pSampleConfiguration->audioCodec = audioCodec; diff --git a/samples/kvsWebrtcClientMasterGstSample.c b/samples/kvsWebrtcClientMasterGstSample.c index bb5036db8a..c7c73c5394 100644 --- a/samples/kvsWebrtcClientMasterGstSample.c +++ b/samples/kvsWebrtcClientMasterGstSample.c @@ -359,6 +359,7 @@ INT32 main(INT32 argc, CHAR* argv[]) PCHAR pChannelName; RTC_CODEC audioCodec = RTC_CODEC_OPUS; RTC_CODEC videoCodec = RTC_CODEC_H264_PROFILE_42E01F_LEVEL_ASYMMETRY_ALLOWED_PACKETIZATION_MODE; + CreateSampleConfigurationParams createSampleConfigurationParams; SET_INSTRUMENTED_ALLOCATORS(); UINT32 logLevel = setLogLevel(); @@ -372,7 +373,14 @@ INT32 main(INT32 argc, CHAR* argv[]) pChannelName = argc > 1 ? argv[1] : SAMPLE_CHANNEL_NAME; #endif - CHK_STATUS(createSampleConfiguration(pChannelName, SIGNALING_CHANNEL_ROLE_TYPE_MASTER, TRUE, TRUE, logLevel, &pSampleConfiguration)); + createSampleConfigurationParams.channelName = pChannelName; + createSampleConfigurationParams.roleType = SIGNALING_CHANNEL_ROLE_TYPE_MASTER; + createSampleConfigurationParams.trickleIce = TRUE; + createSampleConfigurationParams.useTurn = TRUE; + createSampleConfigurationParams.useDualStackEndpoints = FALSE; + createSampleConfigurationParams.logLevel = logLevel; + + CHK_STATUS(createSampleConfiguration(&createSampleConfigurationParams, &pSampleConfiguration)); if (argc > 3 && STRCMP(argv[3], "testsrc") == 0) { if (argc > 4) { 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/Network.c b/src/source/Ice/Network.c index 0bb6f98765..768bc3aa12 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) { diff --git a/src/source/Signaling/ChannelInfo.c b/src/source/Signaling/ChannelInfo.c index 3aba6d57cb..7d6c6d6397 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,7 +173,7 @@ 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 diff --git a/src/source/Signaling/ChannelInfo.h b/src/source/Signaling/ChannelInfo.h index 604b23386e..1341da49cc 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 diff --git a/tst/DualStackEndpointsTest.cpp b/tst/DualStackEndpointsTest.cpp new file mode 100644 index 0000000000..55087752e8 --- /dev/null +++ b/tst/DualStackEndpointsTest.cpp @@ -0,0 +1,143 @@ +#include "WebRTCClientTestFixture.h" + +namespace com { +namespace amazonaws { +namespace kinesis { +namespace video { +namespace webrtcclient { + +class DualStackEndpointsTest : public WebRtcClientTestBase { +}; + +TEST_F(DualStackEndpointsTest, customControlPlaneEndpointBasicCase) +{ + STATUS retStatus; + ChannelInfo channelInfo; + MEMSET(&channelInfo, 0, SIZEOF(channelInfo)); + PChannelInfo pChannelInfo = NULL; + 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(NULL, pChannelInfo); + + // Validate the ChannelInfo's Control Plane URL against the originalCustomControlPlaneUrl. + EXPECT_STREQ(pChannelInfo->pControlPlaneUrl, (PCHAR) originalCustomControlPlaneUrl); + + freeChannelInfo(&pChannelInfo); +} + +TEST_F(DualStackEndpointsTest, customControlPlaneEndpointEdgeCases) +{ + STATUS retStatus; + ChannelInfo channelInfo; + MEMSET(&channelInfo, 0, SIZEOF(channelInfo)); + PChannelInfo pChannelInfo = NULL; + + // 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(NULL, 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(NULL, 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 = (PCHAR) NULL; + + EXPECT_EQ(STATUS_SUCCESS, createValidateChannelInfo(&channelInfo, &pChannelInfo)); + EXPECT_NE(NULL, pChannelInfo); + + EXPECT_STREQ(pChannelInfo->pControlPlaneUrl, (PCHAR) expectedSdkGeneratedUri); + freeChannelInfo(&pChannelInfo); +} + + +TEST_F(DualStackEndpointsTest, customControlPlaneEndpointTooLong) +{ + STATUS retStatus; + ChannelInfo channelInfo; + MEMSET(&channelInfo, 0, SIZEOF(channelInfo)); + PChannelInfo pChannelInfo = NULL; + + + /* 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 From 440fab1684be98a2c00b0c9b64d3bdad426b9dbb Mon Sep 17 00:00:00 2001 From: Stefan Kieszkowski <85728496+stefankiesz@users.noreply.github.com> Date: Tue, 25 Nov 2025 15:04:19 -0800 Subject: [PATCH 07/17] Introduce KVS_DUALSTACK_ENDPOINTS env var --- src/source/Include_i.h | 1 - src/source/Signaling/ChannelInfo.c | 25 +++++++++++++++++++------ src/source/Signaling/ChannelInfo.h | 3 +++ 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/src/source/Include_i.h b/src/source/Include_i.h index 7fe063eb88..1dda2fc70d 100644 --- a/src/source/Include_i.h +++ b/src/source/Include_i.h @@ -115,7 +115,6 @@ typedef struct { KvsIpAddress ipv6Address; } DualKvsIpAddresses, *PDualKvsIpAddresses; -#include static inline BOOL IS_IPV4_ADDR(const KvsIpAddress* pAddress) { diff --git a/src/source/Signaling/ChannelInfo.c b/src/source/Signaling/ChannelInfo.c index 7d6c6d6397..191dd62bd8 100644 --- a/src/source/Signaling/ChannelInfo.c +++ b/src/source/Signaling/ChannelInfo.c @@ -176,12 +176,25 @@ STATUS createValidateChannelInfo(PChannelInfo pOrigChannelInfo, PChannelInfo* pp 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. + 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. + 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 1341da49cc..95ac3233ab 100644 --- a/src/source/Signaling/ChannelInfo.h +++ b/src/source/Signaling/ChannelInfo.h @@ -37,6 +37,9 @@ 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 From dcef8b33053a6efb1d7d9afd97e6d42d66cd0a63 Mon Sep 17 00:00:00 2001 From: Stefan Kieszkowski <85728496+stefankiesz@users.noreply.github.com> Date: Sun, 30 Nov 2025 22:52:45 -0800 Subject: [PATCH 08/17] Revert sample changes --- samples/Common.c | 80 +++++------------------- samples/Samples.h | 13 +--- samples/kvsWebRTCClientMaster.c | 10 +-- samples/kvsWebRTCClientViewer.c | 10 +-- samples/kvsWebRTCClientViewerGstSample.c | 10 +-- samples/kvsWebrtcClientMasterGstSample.c | 10 +-- 6 files changed, 20 insertions(+), 113 deletions(-) diff --git a/samples/Common.c b/samples/Common.c index 83d7907000..a3e2f7d7f1 100644 --- a/samples/Common.c +++ b/samples/Common.c @@ -345,26 +345,6 @@ VOID onIceCandidateHandler(UINT64 customData, PCHAR candidateJson) CHK_LOG_ERR(retStatus); } - -// Using for testing purposes to filter out tcp and turns cases. -BOOL isTurnUdp(const char* url) { - size_t len = STRLEN(url); - if (len < 7) return FALSE; - - // Check start: "turn:" - if (STRNCMP(url, "turn:", 5) != 0) { - return FALSE; - } - - // Check end: "udp" - if (STRCMP(url + len - 3, "udp") != 0) { - return FALSE; - } - - return TRUE; -} - - STATUS initializePeerConnection(PSampleConfiguration pSampleConfiguration, PRtcPeerConnection* ppRtcPeerConnection) { ENTERS(); @@ -383,7 +363,7 @@ STATUS initializePeerConnection(PSampleConfiguration pSampleConfiguration, PRtcP configuration.kvsRtcConfiguration.iceSetInterfaceFilterFunc = NULL; // Set the ICE mode explicitly - configuration.iceTransportPolicy = ICE_TRANSPORT_POLICY_RELAY; + configuration.iceTransportPolicy = ICE_TRANSPORT_POLICY_ALL; #ifdef ENABLE_STATS_CALCULATION_CONTROL configuration.kvsRtcConfiguration.enableIceStats = pSampleConfiguration->enableIceStats; @@ -399,7 +379,7 @@ STATUS initializePeerConnection(PSampleConfiguration pSampleConfiguration, PRtcP pKinesisVideoStunUrlPostFix); if (pSampleConfiguration->useTurn) { - // // Set the URIs from the configuration + // Set the URIs from the configuration CHK_STATUS(signalingClientGetIceConfigInfoCount(pSampleConfiguration->signalingClientHandle, &iceConfigCount)); /* signalingClientGetIceConfigInfoCount can return more than one turn server. Use only one to optimize @@ -418,28 +398,15 @@ 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("Ice server %d urls: %s", j + 1, pIceConfigInfo->uris[j]); - DLOGD("Ice server %d username: %s", j + 1, pIceConfigInfo->password); - DLOGD("Ice server %d credential: %s", j + 1, pIceConfigInfo->userName); - - if (isTurnUdp(pIceConfigInfo->uris[j])) { - DLOGD("Adding ICE server"); - 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); - - uriCount++; - } + 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); + uriCount++; } } - - // For testing purposes: - // STRNCPY(configuration.iceServers[1].urls, "turn:[XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX]?transport=udp", MAX_ICE_CONFIG_URI_LEN); - // STRNCPY(configuration.iceServers[1].urls, "turn:XXX.XXX.XXX.XXX:XXXX?transport=udp", MAX_ICE_CONFIG_URI_LEN); - // STRNCPY(configuration.iceServers[1].credential, "XXXXXXXX", MAX_ICE_CONFIG_CREDENTIAL_LEN); - // STRNCPY(configuration.iceServers[1].username, "XXXXXXXX", MAX_ICE_CONFIG_USER_NAME_LEN); } pSampleConfiguration->iceUriCount = uriCount + 1; @@ -829,13 +796,14 @@ STATUS lookForSslCert(PSampleConfiguration* ppSampleConfiguration) return retStatus; } -STATUS createSampleConfiguration(PCreateSampleConfigurationParams pCreateSampleConfigurationParams, PSampleConfiguration* ppSampleConfiguration) +STATUS createSampleConfiguration(PCHAR channelName, SIGNALING_CHANNEL_ROLE_TYPE roleType, BOOL trickleIce, BOOL useTurn, UINT32 logLevel, + PSampleConfiguration* ppSampleConfiguration) { STATUS retStatus = STATUS_SUCCESS; PCHAR pAccessKey, pSecretKey, pSessionToken; PSampleConfiguration pSampleConfiguration = NULL; - CHK(ppSampleConfiguration != NULL && pCreateSampleConfigurationParams != NULL, STATUS_NULL_ARG); + CHK(ppSampleConfiguration != NULL, STATUS_NULL_ARG); CHK(NULL != (pSampleConfiguration = (PSampleConfiguration) MEMCALLOC(1, SIZEOF(SampleConfiguration))), STATUS_NOT_ENOUGH_MEMORY); @@ -904,41 +872,23 @@ STATUS createSampleConfiguration(PCreateSampleConfigurationParams pCreateSampleC pSampleConfiguration->signalingSendMessageLock = MUTEX_CREATE(FALSE); /* This is ignored for master. Master can extract the info from offer. Viewer has to know if peer can trickle or * not ahead of time. */ - pSampleConfiguration->trickleIce = pCreateSampleConfigurationParams->trickleIce; - pSampleConfiguration->useTurn = pCreateSampleConfigurationParams->useTurn; + pSampleConfiguration->trickleIce = trickleIce; + pSampleConfiguration->useTurn = useTurn; pSampleConfiguration->enableSendingMetricsToViewerViaDc = FALSE; pSampleConfiguration->receiveAudioVideoSource = NULL; pSampleConfiguration->channelInfo.version = CHANNEL_INFO_CURRENT_VERSION; - pSampleConfiguration->channelInfo.pChannelName = pCreateSampleConfigurationParams->channelName; + pSampleConfiguration->channelInfo.pChannelName = channelName; #ifdef IOT_CORE_ENABLE_CREDENTIALS if ((pIotCoreCertificateId = GETENV(IOT_CORE_CERTIFICATE_ID)) != NULL) { pSampleConfiguration->channelInfo.pChannelName = pIotCoreCertificateId; } #endif - - if (pCreateSampleConfigurationParams->useDualStackEndpoints) { - // Create the custom fully qualified control plane endpoint, sans the legacy/dual-stack postfix. - SNPRINTF(pSampleConfiguration->customControlPlaneEndpoint, MAX_CONTROL_PLANE_URI_CHAR_LEN, "%s%s.%s", CONTROL_PLANE_URI_PREFIX, - KINESIS_VIDEO_SERVICE_NAME, pSampleConfiguration->channelInfo.pRegion); - - if (STRSTR(pSampleConfiguration->channelInfo.pRegion, "cn-")) { - STRCAT(pSampleConfiguration->customControlPlaneEndpoint, - CONTROL_PLANE_URI_POSTFIX_CN_DUAL_STACK); // Will use CN region dual-stack endpoint. - } else { - STRCAT(pSampleConfiguration->customControlPlaneEndpoint, CONTROL_PLANE_URI_POSTFIX_DUAL_STACK); // Will use Dual-stack endpoint. - } - - pSampleConfiguration->channelInfo.pControlPlaneUrl = pSampleConfiguration->customControlPlaneEndpoint; - } else { - pSampleConfiguration->channelInfo.pControlPlaneUrl = NULL; // Will use default legacy endpoints. - } - pSampleConfiguration->channelInfo.pKmsKeyId = NULL; pSampleConfiguration->channelInfo.tagCount = 0; pSampleConfiguration->channelInfo.pTags = NULL; pSampleConfiguration->channelInfo.channelType = SIGNALING_CHANNEL_TYPE_SINGLE_MASTER; - pSampleConfiguration->channelInfo.channelRoleType = pCreateSampleConfigurationParams->roleType; + pSampleConfiguration->channelInfo.channelRoleType = roleType; pSampleConfiguration->channelInfo.cachingPolicy = SIGNALING_API_CALL_CACHE_TYPE_FILE; pSampleConfiguration->channelInfo.cachingPeriod = SIGNALING_API_CALL_CACHE_TTL_SENTINEL_VALUE; pSampleConfiguration->channelInfo.asyncIceServerConfig = TRUE; // has no effect @@ -953,7 +903,7 @@ STATUS createSampleConfiguration(PCreateSampleConfigurationParams pCreateSampleC pSampleConfiguration->signalingClientCallbacks.customData = (UINT64) pSampleConfiguration; pSampleConfiguration->clientInfo.version = SIGNALING_CLIENT_INFO_CURRENT_VERSION; - pSampleConfiguration->clientInfo.loggingLevel = pCreateSampleConfigurationParams->logLevel; + pSampleConfiguration->clientInfo.loggingLevel = logLevel; pSampleConfiguration->clientInfo.cacheFilePath = NULL; // Use the default path pSampleConfiguration->clientInfo.signalingClientCreationMaxRetryAttempts = CREATE_SIGNALING_CLIENT_RETRY_ATTEMPTS_SENTINEL_VALUE; pSampleConfiguration->iceCandidatePairStatsTimerId = MAX_UINT32; diff --git a/samples/Samples.h b/samples/Samples.h index ff224b0df1..2e1324d310 100644 --- a/samples/Samples.h +++ b/samples/Samples.h @@ -115,15 +115,6 @@ typedef struct { UINT64 prevTs; } RtcMetricsHistory, *PRtcMetricsHistory; -typedef struct { - PCHAR channelName; - SIGNALING_CHANNEL_ROLE_TYPE roleType; - BOOL trickleIce; - BOOL useTurn; - BOOL useDualStackEndpoints; - UINT32 logLevel; -} CreateSampleConfigurationParams, *PCreateSampleConfigurationParams; - typedef struct { volatile ATOMIC_BOOL appTerminateFlag; volatile ATOMIC_BOOL interrupted; @@ -185,8 +176,6 @@ typedef struct { UINT32 logLevel; BOOL enableTwcc; BOOL enableIceStats; - BOOL useDualStackEndpoints; - CHAR customControlPlaneEndpoint[MAX_CONTROL_PLANE_URI_CHAR_LEN]; } SampleConfiguration, *PSampleConfiguration; typedef struct { @@ -258,7 +247,7 @@ PVOID sampleReceiveAudioVideoFrame(PVOID); PVOID getPeriodicIceCandidatePairStats(PVOID); STATUS getIceCandidatePairStatsCallback(UINT32, UINT64, UINT64); STATUS pregenerateCertTimerCallback(UINT32, UINT64, UINT64); -STATUS createSampleConfiguration(PCreateSampleConfigurationParams, PSampleConfiguration*); +STATUS createSampleConfiguration(PCHAR, SIGNALING_CHANNEL_ROLE_TYPE, BOOL, BOOL, UINT32, PSampleConfiguration*); STATUS freeSampleConfiguration(PSampleConfiguration*); STATUS signalingClientStateChanged(UINT64, SIGNALING_CLIENT_STATE); STATUS signalingMessageReceived(UINT64, PReceivedSignalingMessage); diff --git a/samples/kvsWebRTCClientMaster.c b/samples/kvsWebRTCClientMaster.c index f4dffe10be..5123137b08 100644 --- a/samples/kvsWebRTCClientMaster.c +++ b/samples/kvsWebRTCClientMaster.c @@ -12,7 +12,6 @@ INT32 main(INT32 argc, CHAR* argv[]) signalingClientMetrics.version = SIGNALING_CLIENT_METRICS_CURRENT_VERSION; RTC_CODEC audioCodec = RTC_CODEC_OPUS; RTC_CODEC videoCodec = RTC_CODEC_H264_PROFILE_42E01F_LEVEL_ASYMMETRY_ALLOWED_PACKETIZATION_MODE; - CreateSampleConfigurationParams createSampleConfigurationParams; SET_INSTRUMENTED_ALLOCATORS(); UINT32 logLevel = setLogLevel(); @@ -28,14 +27,7 @@ INT32 main(INT32 argc, CHAR* argv[]) pChannelName = argc > 1 ? argv[1] : SAMPLE_CHANNEL_NAME; #endif - createSampleConfigurationParams.channelName = pChannelName; - createSampleConfigurationParams.roleType = SIGNALING_CHANNEL_ROLE_TYPE_MASTER; - createSampleConfigurationParams.trickleIce = TRUE; - createSampleConfigurationParams.useTurn = TRUE; - createSampleConfigurationParams.useDualStackEndpoints = FALSE; - createSampleConfigurationParams.logLevel = logLevel; - - CHK_STATUS(createSampleConfiguration(&createSampleConfigurationParams, &pSampleConfiguration)); + CHK_STATUS(createSampleConfiguration(pChannelName, SIGNALING_CHANNEL_ROLE_TYPE_MASTER, TRUE, TRUE, logLevel, &pSampleConfiguration)); if (argc > 3) { if (!STRCMP(argv[3], AUDIO_CODEC_NAME_OPUS)) { diff --git a/samples/kvsWebRTCClientViewer.c b/samples/kvsWebRTCClientViewer.c index da5749ff74..a4730d06b1 100644 --- a/samples/kvsWebRTCClientViewer.c +++ b/samples/kvsWebRTCClientViewer.c @@ -44,7 +44,6 @@ INT32 main(INT32 argc, CHAR* argv[]) BOOL locked = FALSE; PCHAR pChannelName; CHAR clientId[256]; - CreateSampleConfigurationParams createSampleConfigurationParams; SET_INSTRUMENTED_ALLOCATORS(); UINT32 logLevel = setLogLevel(); @@ -82,14 +81,7 @@ INT32 main(INT32 argc, CHAR* argv[]) } } - createSampleConfigurationParams.channelName = pChannelName; - createSampleConfigurationParams.roleType = SIGNALING_CHANNEL_ROLE_TYPE_VIEWER; - createSampleConfigurationParams.trickleIce = TRUE; - createSampleConfigurationParams.useTurn = TRUE; - createSampleConfigurationParams.useDualStackEndpoints = FALSE; - createSampleConfigurationParams.logLevel = logLevel; - - CHK_STATUS(createSampleConfiguration(&createSampleConfigurationParams, &pSampleConfiguration)); + CHK_STATUS(createSampleConfiguration(pChannelName, SIGNALING_CHANNEL_ROLE_TYPE_VIEWER, TRUE, TRUE, logLevel, &pSampleConfiguration)); pSampleConfiguration->mediaType = SAMPLE_STREAMING_AUDIO_VIDEO; pSampleConfiguration->audioCodec = audioCodec; pSampleConfiguration->videoCodec = videoCodec; diff --git a/samples/kvsWebRTCClientViewerGstSample.c b/samples/kvsWebRTCClientViewerGstSample.c index 160e323eb6..d31676d1db 100644 --- a/samples/kvsWebRTCClientViewerGstSample.c +++ b/samples/kvsWebRTCClientViewerGstSample.c @@ -44,7 +44,6 @@ INT32 main(INT32 argc, CHAR* argv[]) BOOL locked = FALSE; PCHAR pChannelName; CHAR clientId[256]; - CreateSampleConfigurationParams createSampleConfigurationParams; SET_INSTRUMENTED_ALLOCATORS(); UINT32 logLevel = setLogLevel(); @@ -76,14 +75,7 @@ INT32 main(INT32 argc, CHAR* argv[]) } } - createSampleConfigurationParams.channelName = pChannelName; - createSampleConfigurationParams.roleType = SIGNALING_CHANNEL_ROLE_TYPE_VIEWER; - createSampleConfigurationParams.trickleIce = TRUE; - createSampleConfigurationParams.useTurn = TRUE; - createSampleConfigurationParams.useDualStackEndpoints = FALSE; - createSampleConfigurationParams.logLevel = logLevel; - - CHK_STATUS(createSampleConfiguration(&createSampleConfigurationParams, &pSampleConfiguration)); + CHK_STATUS(createSampleConfiguration(pChannelName, SIGNALING_CHANNEL_ROLE_TYPE_VIEWER, TRUE, TRUE, logLevel, &pSampleConfiguration)); pSampleConfiguration->mediaType = SAMPLE_STREAMING_AUDIO_VIDEO; pSampleConfiguration->receiveAudioVideoSource = receiveGstreamerAudioVideo; pSampleConfiguration->audioCodec = audioCodec; diff --git a/samples/kvsWebrtcClientMasterGstSample.c b/samples/kvsWebrtcClientMasterGstSample.c index c7c73c5394..bb5036db8a 100644 --- a/samples/kvsWebrtcClientMasterGstSample.c +++ b/samples/kvsWebrtcClientMasterGstSample.c @@ -359,7 +359,6 @@ INT32 main(INT32 argc, CHAR* argv[]) PCHAR pChannelName; RTC_CODEC audioCodec = RTC_CODEC_OPUS; RTC_CODEC videoCodec = RTC_CODEC_H264_PROFILE_42E01F_LEVEL_ASYMMETRY_ALLOWED_PACKETIZATION_MODE; - CreateSampleConfigurationParams createSampleConfigurationParams; SET_INSTRUMENTED_ALLOCATORS(); UINT32 logLevel = setLogLevel(); @@ -373,14 +372,7 @@ INT32 main(INT32 argc, CHAR* argv[]) pChannelName = argc > 1 ? argv[1] : SAMPLE_CHANNEL_NAME; #endif - createSampleConfigurationParams.channelName = pChannelName; - createSampleConfigurationParams.roleType = SIGNALING_CHANNEL_ROLE_TYPE_MASTER; - createSampleConfigurationParams.trickleIce = TRUE; - createSampleConfigurationParams.useTurn = TRUE; - createSampleConfigurationParams.useDualStackEndpoints = FALSE; - createSampleConfigurationParams.logLevel = logLevel; - - CHK_STATUS(createSampleConfiguration(&createSampleConfigurationParams, &pSampleConfiguration)); + CHK_STATUS(createSampleConfiguration(pChannelName, SIGNALING_CHANNEL_ROLE_TYPE_MASTER, TRUE, TRUE, logLevel, &pSampleConfiguration)); if (argc > 3 && STRCMP(argv[3], "testsrc") == 0) { if (argc > 4) { From 4626f93931017b5040eb430bcc9883f1dce09101 Mon Sep 17 00:00:00 2001 From: Stefan Kieszkowski <85728496+stefankiesz@users.noreply.github.com> Date: Mon, 1 Dec 2025 01:04:47 -0800 Subject: [PATCH 09/17] TURN parsing implementation --- src/source/Ice/Network.c | 182 ++++++++++++++++++++++++++++++++++----- 1 file changed, 159 insertions(+), 23 deletions(-) diff --git a/src/source/Ice/Network.c b/src/source/Ice/Network.c index 768bc3aa12..728343fa39 100644 --- a/src/source/Ice/Network.c +++ b/src/source/Ice/Network.c @@ -336,17 +336,45 @@ STATUS socketWrite(INT32 sockfd, const void* pBuffer, SIZE_T length) BOOL isIpAddr(PCHAR hostname, UINT16 length) { - BOOL status = TRUE; + UINT16 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; } - return status; + + // 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; + } + + // TODO: Remove this once sure not keeping. + // Check if IPv6 address within brackets. + // if (hostname[0] == '[' && hostname[strlen(hostname) - 1] == ']') { + // offset = 0; + // if (sscanf(hostname + 1, "%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 == length - 2) { + // 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 FALSE; } STATUS getIpAddrFromDnsHostname(PCHAR hostname, PCHAR address, UINT16 lengthSrc, UINT16 maxLenDst) @@ -361,7 +389,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,6 +419,65 @@ STATUS getIpAddrFromDnsHostname(PCHAR hostname, PCHAR address, UINT16 lengthSrc, return retStatus; } +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); + + + // 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."); + + if (hostname[i] >= '0' && hostname[i] <= '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."); + + if (hostname[i] >= '0' && hostname[i] <= '9') { + 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; @@ -404,11 +490,15 @@ STATUS getIpWithHostName(PCHAR hostname, PDualKvsIpAddresses destIps) struct sockaddr_in* ipv4Addr; struct sockaddr_in6* ipv6Addr; struct in_addr inaddr; + struct in6_addr in6addr; CHAR ipv4AddrStr[KVS_IP_ADDRESS_STRING_BUFFER_LEN]; CHAR ipv6AddrStr[KVS_IP_ADDRESS_STRING_BUFFER_LEN]; - 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 wasAddressParsed = FALSE; CHK(hostname != NULL, STATUS_NULL_ARG); DLOGI("ICE SERVER Hostname received: %s", hostname); @@ -421,16 +511,38 @@ STATUS getIpWithHostName(PCHAR hostname, PDualKvsIpAddresses destIps) // 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); + if (hostname[0] == '[' && hostname[hostnameLen - 1] == ']') { + MEMCPY(addr, hostname + 1, hostnameLen - 2); + addr[hostnameLen - 2] = '\0'; + } else { + MEMCPY(addr, hostname, hostnameLen); + addr[hostnameLen] = '\0'; + } } else if (!isStunServer) { - retStatus = getIpAddrFromDnsHostname(hostname, addr, hostnameLen, addrLen); + if (GETENV(USE_DUAL_STACK_ENDPOINTS_ENV_VAR) != NULL) { + 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); + } + } } + wasAddressParsed = isIpAddr(addr, STRLEN(addr)) || (isIpAddr(ipv4Addr, STRLEN(ipv4Addr)) && isIpAddr(ipv6Addr, STRLEN(ipv6Addr))); + // 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 + if (!wasAddressParsed || 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); } errCode = getaddrinfo(hostname, NULL, NULL, &res); @@ -466,12 +578,37 @@ STATUS getIpWithHostName(PCHAR hostname, PDualKvsIpAddresses destIps) freeaddrinfo(res); CHK_ERR(ipv4Resolved || ipv6Resolved, STATUS_HOSTNAME_NOT_FOUND, "Could not find network address of %s", hostname); } else { - // TODO: The below is for TURN case, will need to do this based on IP family too... - // NOTE: Current design plan is to not send TURN host names with prepended IP addressses, so this will not need to change - // and will remain for backwards compat with the legacy servers. - inet_pton(AF_INET, addr, &inaddr); - destIps->ipv4Address.family = KVS_IP_FAMILY_TYPE_IPV4; - MEMCPY(destIps->ipv4Address.address, &inaddr, IPV4_ADDRESS_LENGTH); + + 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", addr); + 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", addr); + retStatus = STATUS_INVALID_ARG; + } + + } 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 { + DLOGW("inet_pton failed on legacy ICE server address: %s", addr); + retStatus = STATUS_INVALID_ARG; + } + } } CleanUp: @@ -506,7 +643,6 @@ STATUS getIpAddrStr(PKvsIpAddress pKvsIpAddress, PCHAR pBuffer, UINT32 bufferLen return retStatus; } - BOOL isSameIpAddress(PKvsIpAddress pAddr1, PKvsIpAddress pAddr2, BOOL checkPort) { BOOL ret; From b78ea3de3d5e5d50778618c0742b6501f01ef341 Mon Sep 17 00:00:00 2001 From: Stefan Kieszkowski <85728496+stefankiesz@users.noreply.github.com> Date: Mon, 1 Dec 2025 12:42:04 -0800 Subject: [PATCH 10/17] Add ip family check to filter peers added to turn connection --- src/source/Ice/TurnConnection.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/source/Ice/TurnConnection.c b/src/source/Ice/TurnConnection.c index 84fab21ce3..0d1752a696 100644 --- a/src/source/Ice/TurnConnection.c +++ b/src/source/Ice/TurnConnection.c @@ -731,8 +731,12 @@ STATUS turnConnectionAddPeer(PTurnConnection pTurnConnection, PKvsIpAddress pPee BOOL locked = FALSE; CHK(pTurnConnection != NULL && pPeerAddress != NULL, STATUS_NULL_ARG); - // CHK(pTurnConnection->turnServer.ipAddresses.ipv4Address.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; From e3f592aeec9f370d5ecbffc08f08ed6d395d147b Mon Sep 17 00:00:00 2001 From: Stefan Kieszkowski <85728496+stefankiesz@users.noreply.github.com> Date: Mon, 1 Dec 2025 15:24:52 -0800 Subject: [PATCH 11/17] Cleanup, address comment, hide more dual-stack paths behind the envvar --- .../libkvsCommonLws-CMakeLists.txt | 2 +- src/source/Ice/IceAgent.c | 65 ++++++++++--------- src/source/Ice/IceUtils.c | 6 +- src/source/Ice/Network.c | 13 ---- src/source/PeerConnection/PeerConnection.c | 5 ++ src/source/Signaling/ChannelInfo.c | 2 + src/source/Stun/Stun.h | 2 +- 7 files changed, 46 insertions(+), 49 deletions(-) diff --git a/CMake/Dependencies/libkvsCommonLws-CMakeLists.txt b/CMake/Dependencies/libkvsCommonLws-CMakeLists.txt index 3ba4f9646d..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 dual-stack-support + GIT_TAG v1.6.0 GIT_PROGRESS TRUE GIT_SHALLOW TRUE PREFIX ${CMAKE_CURRENT_BINARY_DIR}/build diff --git a/src/source/Ice/IceAgent.c b/src/source/Ice/IceAgent.c index 1e686d55d7..5bdd58b544 100644 --- a/src/source/Ice/IceAgent.c +++ b/src/source/Ice/IceAgent.c @@ -1441,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 @@ -1459,38 +1460,29 @@ STATUS iceAgentSendSrflxCandidateRequest(PIceAgent pIceAgent) switch (pCandidate->iceCandidateType) { case ICE_CANDIDATE_TYPE_SERVER_REFLEXIVE: pIceServer = &(pIceAgent->iceServers[pCandidate->iceServerIndex]); - if (pIceServer->ipAddresses.ipv4Address.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)); - - DLOGD("Sending STUN binding request to IPv4 STUN server: %u:%u", pIceServer->ipAddresses.ipv4Address.address, - pIceServer->ipAddresses.ipv4Address.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())); - } - } else if (pIceServer->ipAddresses.ipv6Address.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)); - - DLOGD("Sending STUN binding request to IPv6 STUN server: %u:%u", pIceServer->ipAddresses.ipv6Address.address, - pIceServer->ipAddresses.ipv6Address.port); - - CHK_STATUS(iceAgentSendStunPacket(pBindingRequest, NULL, 0, pIceAgent, pCandidate, &pIceServer->ipAddresses.ipv6Address)); - 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; @@ -1783,6 +1775,12 @@ STATUS iceAgentInitSrflxCandidate(PIceAgent pIceAgent) if (pCandidate->iceCandidateType == ICE_CANDIDATE_TYPE_HOST) { for (j = 0; j < pIceAgent->iceServersCount; j++) { pIceServer = &pIceAgent->iceServers[j]; + + 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)) { @@ -1859,6 +1857,9 @@ STATUS iceAgentInitRelayCandidates(PIceAgent pIceAgent) for (j = 0; j < pIceAgent->iceServersCount; j++) { if (pIceAgent->iceServers[j].isTurn) { + 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)); diff --git a/src/source/Ice/IceUtils.c b/src/source/Ice/IceUtils.c index 3c84664a42..091591b587 100644 --- a/src/source/Ice/IceUtils.c +++ b/src/source/Ice/IceUtils.c @@ -176,10 +176,12 @@ STATUS iceUtilsSendStunPacket(PStunPacket pStunPacket, PBYTE password, UINT32 pa CHK(pDest != NULL, STATUS_NULL_ARG); switch (pStunPacket->header.stunMessageType) { case STUN_PACKET_TYPE_BINDING_REQUEST: - DLOGD("Sending BINDING_REQUEST to ip:%s, port:%u", ipAddrStr, (UINT16) getInt16(pDest->port)); + DLOGD("Sending BINDING_REQUEST on socket id: %d, to ip:%u.%u.%u.%u, port:%u", pSocketConnection->localSocket, pDest->address[0], + pDest->address[1], pDest->address[2], pDest->address[3], (UINT16) getInt16(pDest->port)); break; case STUN_PACKET_TYPE_BINDING_RESPONSE_SUCCESS: - DLOGD("Sending BINDING_RESPONSE_SUCCESS to ip:%s, port:%u", ipAddrStr, (UINT16) getInt16(pDest->port)); + DLOGD("Sending BINDING_RESPONSE_SUCCESS on socket id: %d to ip:%u.%u.%u.%u, port:%u", pSocketConnection->localSocket, pDest->address[0], + pDest->address[1], pDest->address[2], pDest->address[3], (UINT16) getInt16(pDest->port)); break; default: break; diff --git a/src/source/Ice/Network.c b/src/source/Ice/Network.c index 728343fa39..d966acdb20 100644 --- a/src/source/Ice/Network.c +++ b/src/source/Ice/Network.c @@ -354,19 +354,6 @@ BOOL isIpAddr(PCHAR hostname, UINT16 length) return TRUE; } - // TODO: Remove this once sure not keeping. - // Check if IPv6 address within brackets. - // if (hostname[0] == '[' && hostname[strlen(hostname) - 1] == ']') { - // offset = 0; - // if (sscanf(hostname + 1, "%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 == length - 2) { - // 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 && diff --git a/src/source/PeerConnection/PeerConnection.c b/src/source/PeerConnection/PeerConnection.c index c1fcbfe355..926301651b 100644 --- a/src/source/PeerConnection/PeerConnection.c +++ b/src/source/PeerConnection/PeerConnection.c @@ -817,6 +817,11 @@ STATUS getStunAddr(PStunIpAddrContext pStunIpAddrCtx) 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); diff --git a/src/source/Signaling/ChannelInfo.c b/src/source/Signaling/ChannelInfo.c index 191dd62bd8..ca56554b86 100644 --- a/src/source/Signaling/ChannelInfo.c +++ b/src/source/Signaling/ChannelInfo.c @@ -178,6 +178,7 @@ STATUS createValidateChannelInfo(PChannelInfo pOrigChannelInfo, PChannelInfo* pp } else { 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); @@ -188,6 +189,7 @@ STATUS createValidateChannelInfo(PChannelInfo pOrigChannelInfo, PChannelInfo* pp } 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); diff --git a/src/source/Stun/Stun.h b/src/source/Stun/Stun.h index 10cc66d2d5..a3f4925f10 100644 --- a/src/source/Stun/Stun.h +++ b/src/source/Stun/Stun.h @@ -72,7 +72,7 @@ extern "C" { */ #define STUN_ATTRIBUTE_REQUESTED_TRANSPORT_PROTOCOL_LEN (UINT16) 4 -/**f +/** * Requested address family attribute value length = 4 bytes = 32 bits */ #define STUN_ATTRIBUTE_REQUESTED_ADDRESS_FAMILY_LEN (UINT16) 4 From 62e3bcaa7b72dd8c729c6c8d7d35ef32e9203b53 Mon Sep 17 00:00:00 2001 From: Stefan Kieszkowski <85728496+stefankiesz@users.noreply.github.com> Date: Mon, 1 Dec 2025 16:53:03 -0800 Subject: [PATCH 12/17] Update getIpWithHostName func --- src/source/Ice/Network.c | 53 +++++++++++++++++++++++----------------- 1 file changed, 31 insertions(+), 22 deletions(-) diff --git a/src/source/Ice/Network.c b/src/source/Ice/Network.c index d966acdb20..cb155c14d7 100644 --- a/src/source/Ice/Network.c +++ b/src/source/Ice/Network.c @@ -474,18 +474,17 @@ STATUS getIpWithHostName(PCHAR hostname, PDualKvsIpAddresses destIps) struct addrinfo *res, *rp; BOOL ipv4Resolved = FALSE; BOOL ipv6Resolved = FALSE; - struct sockaddr_in* ipv4Addr; - struct sockaddr_in6* ipv6Addr; + struct sockaddr_in* ipv4SockAddr; + struct sockaddr_in6* ipv6SockAddr; struct in_addr inaddr; struct in6_addr in6addr; - CHAR ipv4AddrStr[KVS_IP_ADDRESS_STRING_BUFFER_LEN]; - CHAR ipv6AddrStr[KVS_IP_ADDRESS_STRING_BUFFER_LEN]; 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 wasAddressParsed = FALSE; + BOOL wasAddressParseSuccessful = FALSE; + BOOL dualStackEnvVarSet = FALSE; CHK(hostname != NULL, STATUS_NULL_ARG); DLOGI("ICE SERVER Hostname received: %s", hostname); @@ -493,6 +492,7 @@ STATUS getIpWithHostName(PCHAR hostname, PDualKvsIpAddresses destIps) 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 @@ -506,7 +506,9 @@ STATUS getIpWithHostName(PCHAR hostname, PDualKvsIpAddresses destIps) addr[hostnameLen] = '\0'; } } else if (!isStunServer) { - if (GETENV(USE_DUAL_STACK_ENDPOINTS_ENV_VAR) != NULL) { + // 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)); @@ -523,38 +525,43 @@ STATUS getIpWithHostName(PCHAR hostname, PDualKvsIpAddresses destIps) } } - wasAddressParsed = isIpAddr(addr, STRLEN(addr)) || (isIpAddr(ipv4Addr, STRLEN(ipv4Addr)) && isIpAddr(ipv6Addr, STRLEN(ipv6Addr))); + wasAddressParseSuccessful = isIpAddr(addr, STRLEN(addr)) || (isIpAddr(ipv4Addr, STRLEN(ipv4Addr)) && isIpAddr(ipv6Addr, STRLEN(ipv6Addr))); - // Verify the generated address has the format x.x.x.x - if (!wasAddressParsed || retStatus != STATUS_SUCCESS) { + // 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 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); if (errCode != 0) { 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 && !(ipv4Resolved && ipv6Resolved); rp = rp->ai_next) { - if (rp->ai_family == AF_INET) { - ipv4Addr = (struct sockaddr_in*) rp->ai_addr; + 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, &ipv4Addr->sin_addr, IPV4_ADDRESS_LENGTH); + MEMCPY(destIps->ipv4Address.address, &ipv4SockAddr->sin_addr, IPV4_ADDRESS_LENGTH); - CHK_STATUS(getIpAddrStr(&(destIps->ipv4Address), ipv4AddrStr, ARRAY_SIZE(ipv4AddrStr))); - DLOGD("Found an IPv4 ICE server addresss: %s", ipv4AddrStr); + 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) { - ipv6Addr = (struct sockaddr_in6*) rp->ai_addr; + } 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, &ipv6Addr->sin6_addr, IPV6_ADDRESS_LENGTH); + MEMCPY(destIps->ipv6Address.address, &ipv6SockAddr->sin6_addr, IPV6_ADDRESS_LENGTH); - CHK_STATUS(getIpAddrStr(&(destIps->ipv6Address), ipv6AddrStr, ARRAY_SIZE(ipv6AddrStr))); - DLOGD("Found an IPv6 ICE server addresss: %s", ipv6AddrStr); + CHK_STATUS(getIpAddrStr(&(destIps->ipv6Address), ipv6Addr, ARRAY_SIZE(ipv6Addr))); + DLOGD("Found an IPv6 ICE server addresss: %s", ipv6Addr); ipv6Resolved = TRUE; @@ -565,7 +572,9 @@ STATUS getIpWithHostName(PCHAR hostname, PDualKvsIpAddresses destIps) freeaddrinfo(res); 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. @@ -573,15 +582,15 @@ STATUS getIpWithHostName(PCHAR hostname, PDualKvsIpAddresses destIps) 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", addr); + 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", addr); + DLOGW("inet_pton failed on IPv6 ICE server address: %s", ipv6Addr); retStatus = STATUS_INVALID_ARG; } From e1d11bb60499194075ae75a1cafbf50f4faecd0a Mon Sep 17 00:00:00 2001 From: Stefan Kieszkowski <85728496+stefankiesz@users.noreply.github.com> Date: Tue, 2 Dec 2025 01:57:56 -0800 Subject: [PATCH 13/17] NULL -> nullptr for unit tests --- tst/DualStackEndpointsTest.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tst/DualStackEndpointsTest.cpp b/tst/DualStackEndpointsTest.cpp index 55087752e8..21a3a62eef 100644 --- a/tst/DualStackEndpointsTest.cpp +++ b/tst/DualStackEndpointsTest.cpp @@ -14,7 +14,7 @@ TEST_F(DualStackEndpointsTest, customControlPlaneEndpointBasicCase) STATUS retStatus; ChannelInfo channelInfo; MEMSET(&channelInfo, 0, SIZEOF(channelInfo)); - PChannelInfo pChannelInfo = NULL; + 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}; @@ -28,7 +28,7 @@ TEST_F(DualStackEndpointsTest, customControlPlaneEndpointBasicCase) channelInfo.pChannelName = "TestChannelName"; EXPECT_EQ(STATUS_SUCCESS, createValidateChannelInfo(&channelInfo, &pChannelInfo)); - EXPECT_NE(NULL, pChannelInfo); + EXPECT_NE(nullptr, pChannelInfo); // Validate the ChannelInfo's Control Plane URL against the originalCustomControlPlaneUrl. EXPECT_STREQ(pChannelInfo->pControlPlaneUrl, (PCHAR) originalCustomControlPlaneUrl); @@ -41,7 +41,7 @@ TEST_F(DualStackEndpointsTest, customControlPlaneEndpointEdgeCases) STATUS retStatus; ChannelInfo channelInfo; MEMSET(&channelInfo, 0, SIZEOF(channelInfo)); - PChannelInfo pChannelInfo = NULL; + 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. @@ -74,7 +74,7 @@ TEST_F(DualStackEndpointsTest, customControlPlaneEndpointEdgeCases) channelInfo.pControlPlaneUrl = (PCHAR) validatedCustomControlPlaneUrl; EXPECT_EQ(STATUS_SUCCESS, createValidateChannelInfo(&channelInfo, &pChannelInfo)); - EXPECT_NE(NULL, pChannelInfo); + EXPECT_NE(nullptr, pChannelInfo); // Validate the ChannelInfo's Control Plane URL against originalCustomControlPlaneUrl in case createValidateChannelInfo // modified validatedCustomControlPlaneUrl. @@ -90,7 +90,7 @@ TEST_F(DualStackEndpointsTest, customControlPlaneEndpointEdgeCases) channelInfo.pControlPlaneUrl = (PCHAR) validatedCustomControlPlaneUrl; EXPECT_EQ(STATUS_SUCCESS, createValidateChannelInfo(&channelInfo, &pChannelInfo)); - EXPECT_NE(NULL, 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); @@ -99,10 +99,10 @@ TEST_F(DualStackEndpointsTest, customControlPlaneEndpointEdgeCases) /* NULL URL ARRAY (expect SDK to construct a proper URL) */ - channelInfo.pControlPlaneUrl = (PCHAR) NULL; + channelInfo.pControlPlaneUrl = nullptr; EXPECT_EQ(STATUS_SUCCESS, createValidateChannelInfo(&channelInfo, &pChannelInfo)); - EXPECT_NE(NULL, pChannelInfo); + EXPECT_NE(nullptr, pChannelInfo); EXPECT_STREQ(pChannelInfo->pControlPlaneUrl, (PCHAR) expectedSdkGeneratedUri); freeChannelInfo(&pChannelInfo); @@ -114,7 +114,7 @@ TEST_F(DualStackEndpointsTest, customControlPlaneEndpointTooLong) STATUS retStatus; ChannelInfo channelInfo; MEMSET(&channelInfo, 0, SIZEOF(channelInfo)); - PChannelInfo pChannelInfo = NULL; + PChannelInfo pChannelInfo = nullptr; /* Exceed MAX URL ARRAY LENGTH (expect SDK to error out) */ From 23aa7dfb10c013e37ee2c0618f26ef374605d682 Mon Sep 17 00:00:00 2001 From: Stefan Kieszkowski <85728496+stefankiesz@users.noreply.github.com> Date: Tue, 2 Dec 2025 09:19:13 -0800 Subject: [PATCH 14/17] Send turn allocation family attribute, fix test build --- src/source/Ice/TurnConnection.c | 9 +++++---- src/source/Ice/TurnConnection.h | 2 +- src/source/Ice/TurnConnectionStateMachine.c | 4 ++-- tst/TurnConnectionFunctionalityTest.cpp | 2 +- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/source/Ice/TurnConnection.c b/src/source/Ice/TurnConnection.c index 0d1752a696..cfec2fb1dd 100644 --- a/src/source/Ice/TurnConnection.c +++ b/src/source/Ice/TurnConnection.c @@ -1223,7 +1223,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; @@ -1241,10 +1241,11 @@ STATUS turnConnectionPackageTurnAllocationRequest(PCHAR username, PCHAR realm, P CHK_STATUS(appendStunUsernameAttribute(pTurnAllocateRequest, username)); CHK_STATUS(appendStunRealmAttribute(pTurnAllocateRequest, realm)); CHK_STATUS(appendStunNonceAttribute(pTurnAllocateRequest, nonce, nonceLen)); - // CHK_STATUS(appendStunAllocationAddressFamily(pTurnAllocateRequest, KVS_IP_FAMILY_TYPE_IPV4)); - // Note: No longer planning to use this attribute, IP-family will be determined - // to match the socket connection between client and TURN server. + // 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: diff --git a/src/source/Ice/TurnConnection.h b/src/source/Ice/TurnConnection.h index 816ab52bb4..dc3ba1bcdc 100644 --- a/src/source/Ice/TurnConnection.h +++ b/src/source/Ice/TurnConnection.h @@ -207,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); diff --git a/src/source/Ice/TurnConnectionStateMachine.c b/src/source/Ice/TurnConnectionStateMachine.c index 7426f053de..cbf16c29cf 100644 --- a/src/source/Ice/TurnConnectionStateMachine.c +++ b/src/source/Ice/TurnConnectionStateMachine.c @@ -207,7 +207,7 @@ 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)); + turnConnectionPackageTurnAllocationRequest(NULL, NULL, NULL, 0, DEFAULT_TURN_ALLOCATION_LIFETIME_SECONDS, &pTurnConnection->pTurnPacket, pTurnConnection->ipFamilyType)); } CleanUp: @@ -347,7 +347,7 @@ STATUS executeAllocationTurnState(UINT64 customData, UINT64 time) SIZEOF(pTurnConnection->longTermKey))); CHK_STATUS(turnConnectionPackageTurnAllocationRequest(pTurnConnection->turnServer.username, pTurnConnection->turnRealm, pTurnConnection->turnNonce, pTurnConnection->nonceLen, - DEFAULT_TURN_ALLOCATION_LIFETIME_SECONDS, &pTurnConnection->pTurnPacket)); + DEFAULT_TURN_ALLOCATION_LIFETIME_SECONDS, &pTurnConnection->pTurnPacket, pTurnConnection->ipFamilyType)); pTurnConnection->state = TURN_STATE_ALLOCATION; } else { CHK(currentTime <= pTurnConnection->stateTimeoutTime, STATUS_TURN_CONNECTION_ALLOCATION_FAILED); diff --git a/tst/TurnConnectionFunctionalityTest.cpp b/tst/TurnConnectionFunctionalityTest.cpp index 42a158b42a..e9bb9dc182 100644 --- a/tst/TurnConnectionFunctionalityTest.cpp +++ b/tst/TurnConnectionFunctionalityTest.cpp @@ -74,7 +74,7 @@ class TurnConnectionFunctionalityTest : public WebRtcClientTestBase { 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)); } From 189beb074806736e90adcdcc2a32199dcee577cf Mon Sep 17 00:00:00 2001 From: Stefan Kieszkowski <85728496+stefankiesz@users.noreply.github.com> Date: Tue, 2 Dec 2025 10:53:31 -0800 Subject: [PATCH 15/17] Handle hard-coded IPv6 ICE server address case. --- src/source/Ice/Network.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/source/Ice/Network.c b/src/source/Ice/Network.c index cb155c14d7..41b7f6edee 100644 --- a/src/source/Ice/Network.c +++ b/src/source/Ice/Network.c @@ -498,13 +498,8 @@ STATUS getIpWithHostName(PCHAR hostname, PDualKvsIpAddresses destIps) // there is no way this function would receive an address directly, but having this check // in place anyways if (isIpAddr(hostname, hostnameLen)) { - if (hostname[0] == '[' && hostname[hostnameLen - 1] == ']') { - MEMCPY(addr, hostname + 1, hostnameLen - 2); - addr[hostnameLen - 2] = '\0'; - } else { MEMCPY(addr, hostname, hostnameLen); addr[hostnameLen] = '\0'; - } } else if (!isStunServer) { // Try to parse the address from the TURN server hostname. @@ -600,6 +595,11 @@ STATUS getIpWithHostName(PCHAR hostname, PDualKvsIpAddresses destIps) 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; From bffdfc01806a74fddc095f323614173537340eea Mon Sep 17 00:00:00 2001 From: Stefan Kieszkowski <85728496+stefankiesz@users.noreply.github.com> Date: Tue, 2 Dec 2025 11:46:30 -0800 Subject: [PATCH 16/17] Fix buffer overflow --- src/source/Ice/Network.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/source/Ice/Network.c b/src/source/Ice/Network.c index 41b7f6edee..ef65f3f1ee 100644 --- a/src/source/Ice/Network.c +++ b/src/source/Ice/Network.c @@ -336,7 +336,7 @@ STATUS socketWrite(INT32 sockfd, const void* pBuffer, SIZE_T length) BOOL isIpAddr(PCHAR hostname, UINT16 length) { - UINT16 offset = 0; + UINT32 offset = 0; UINT32 ip_1, ip_2, ip_3, ip_4, ip_5, ip_6, ip_7, ip_8; if (hostname == NULL) { From d4cd9712c7e006f0d55639c1b657a433dae9feae Mon Sep 17 00:00:00 2001 From: Stefan Kieszkowski <85728496+stefankiesz@users.noreply.github.com> Date: Tue, 2 Dec 2025 22:30:13 -0800 Subject: [PATCH 17/17] Add unit test coverage, minor fixes --- src/source/Ice/Network.c | 14 +++- tst/CustomEndpointTest.cpp | 143 +++++++++++++++++++++++++++++++++ tst/DualStackEndpointsTest.cpp | 133 +++++------------------------- tst/NetworkApiTest.cpp | 6 ++ 4 files changed, 180 insertions(+), 116 deletions(-) create mode 100644 tst/CustomEndpointTest.cpp diff --git a/src/source/Ice/Network.c b/src/source/Ice/Network.c index ef65f3f1ee..4120770d1e 100644 --- a/src/source/Ice/Network.c +++ b/src/source/Ice/Network.c @@ -406,6 +406,7 @@ STATUS getIpAddrFromDnsHostname(PCHAR hostname, PCHAR address, UINT16 lengthSrc, return retStatus; } +// 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; @@ -413,6 +414,7 @@ STATUS getDualStackIpAddrFromDnsHostname(PCHAR hostname, PCHAR ipv4Address, PCHA 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: @@ -423,7 +425,9 @@ STATUS getDualStackIpAddrFromDnsHostname(PCHAR hostname, PCHAR ipv4Address, PCHA CHK_WARN(j < maxLenV4Dst, STATUS_INVALID_ADDRESS_LENGTH, "Generated IPv4 address is past allowed size."); - if (hostname[i] >= '0' && hostname[i] <= '9') { + c = hostname[i]; + + if (c >= '0' && c <= '9') { ipv4Address[j] = hostname[i]; } else if (hostname[i] == '-') { ipv4Address[j] = '.'; @@ -447,7 +451,11 @@ STATUS getDualStackIpAddrFromDnsHostname(PCHAR hostname, PCHAR ipv4Address, PCHA CHK_WARN(j < maxLenV6Dst, STATUS_INVALID_ADDRESS_LENGTH, "Generated IPv6 address is past allowed size."); - if (hostname[i] >= '0' && hostname[i] <= '9') { + 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] = ':'; @@ -560,8 +568,6 @@ STATUS getIpWithHostName(PCHAR hostname, PDualKvsIpAddresses destIps) ipv6Resolved = TRUE; - } else { - DLOGD("Found an invalid ICE server addresss family type - must be IPv4 or IPv6."); } } freeaddrinfo(res); 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 index 21a3a62eef..5c5fac92c5 100644 --- a/tst/DualStackEndpointsTest.cpp +++ b/tst/DualStackEndpointsTest.cpp @@ -6,138 +6,47 @@ namespace kinesis { namespace video { namespace webrtcclient { -class DualStackEndpointsTest : public WebRtcClientTestBase { -}; +class DualStackEndpointsTest : public WebRtcClientTestBase {}; -TEST_F(DualStackEndpointsTest, 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(DualStackEndpointsTest, customControlPlaneEndpointEdgeCases) +TEST_F(DualStackEndpointsTest, connectTwoDualStackPeersWithForcedTurn) { - 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'; + // (This fails because service is not yet ready.) - // 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; + // RtcConfiguration configuration; + // PRtcPeerConnection offerPc = NULL, answerPc = NULL; - EXPECT_EQ(STATUS_SUCCESS, createValidateChannelInfo(&channelInfo, &pChannelInfo)); - EXPECT_NE(nullptr, pChannelInfo); + // ASSERT_EQ(TRUE, mAccessKeyIdSet); - // Validate the ChannelInfo's Control Plane URL against originalCustomControlPlaneUrl in case createValidateChannelInfo - // modified validatedCustomControlPlaneUrl. - EXPECT_STREQ(pChannelInfo->pControlPlaneUrl, (PCHAR) originalCustomControlPlaneUrl); - freeChannelInfo(&pChannelInfo); + // setenv(USE_DUAL_STACK_ENDPOINTS_ENV_VAR, "ON", 1); + // MEMSET(&configuration, 0x00, SIZEOF(RtcConfiguration)); + // configuration.iceTransportPolicy = ICE_TRANSPORT_POLICY_RELAY; - /* EMPTY URL ARRAY (expect SDK to construct a proper URL) */ + // initializeSignalingClient(); + // getIceServers(&configuration); - // Fill array with null values. - MEMSET(validatedCustomControlPlaneUrl, 0, SIZEOF(validatedCustomControlPlaneUrl)); + // EXPECT_EQ(createPeerConnection(&configuration, &offerPc), STATUS_SUCCESS); + // EXPECT_EQ(createPeerConnection(&configuration, &answerPc), STATUS_SUCCESS); - channelInfo.pControlPlaneUrl = (PCHAR) validatedCustomControlPlaneUrl; + // EXPECT_EQ(connectTwoPeers(offerPc, answerPc), TRUE); - EXPECT_EQ(STATUS_SUCCESS, createValidateChannelInfo(&channelInfo, &pChannelInfo)); - EXPECT_NE(nullptr, pChannelInfo); + // closePeerConnection(offerPc); + // closePeerConnection(answerPc); - // Validate the ChannelInfo's Control Plane URL should be the default one the SDK generated. - EXPECT_STREQ(pChannelInfo->pControlPlaneUrl, (PCHAR) expectedSdkGeneratedUri); - freeChannelInfo(&pChannelInfo); + // freePeerConnection(&offerPc); + // freePeerConnection(&answerPc); + // deinitializeSignalingClient(); - /* 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); + // unsetenv(USE_DUAL_STACK_ENDPOINTS_ENV_VAR); } -TEST_F(DualStackEndpointsTest, 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 +} // namespace com \ No newline at end of file diff --git a/tst/NetworkApiTest.cpp b/tst/NetworkApiTest.cpp index d958fe64c4..bea64eda4b 100644 --- a/tst/NetworkApiTest.cpp +++ b/tst/NetworkApiTest.cpp @@ -15,6 +15,12 @@ TEST_F(NetworkApiTest, GetIpWithHostNameTest) 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));