Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .github/workflows/samples.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ jobs:
env:
AWS_KVS_LOG_LEVEL: 2

strategy:
matrix:
endpoint-type: [ "legacy", "dual-stack" ]

permissions:
id-token: write
contents: read
Expand All @@ -36,6 +40,15 @@ jobs:
role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }}
aws-region: ${{ secrets.AWS_REGION }}

- name: Set endpoint type
run: |
if [ "${{ matrix.endpoint-type }}" = "dual-stack" ]; then
echo "Using dual-stack endpoints"
echo "USE_DUAL_STACK_ENDPOINTS=ON" >> $GITHUB_ENV
else
echo "Using legacy endpoints"
fi

- name: Build repository
run: |
mkdir build && cd build
Expand Down
2 changes: 1 addition & 1 deletion CMake/Dependencies/libkvsCommonLws-CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ include(ExternalProject)

ExternalProject_Add(libkvsCommonLws-download
GIT_REPOSITORY https://github.com/awslabs/amazon-kinesis-video-streams-producer-c.git
GIT_TAG v1.5.4
GIT_TAG v1.6.0
GIT_PROGRESS TRUE
GIT_SHALLOW TRUE
PREFIX ${CMAKE_CURRENT_BINARY_DIR}/build
Expand Down
1 change: 1 addition & 0 deletions CMake/Dependencies/libwebsockets-CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ ExternalProject_Add(project_libwebsockets
-DLWS_EXT_PTHREAD_LIBRARIES=${LWS_EXT_PTHREAD_LIBRARIES}
-DLWS_OPENSSL_INCLUDE_DIRS=${LWS_OPENSSL_INCLUDE_DIRS}
-DLWS_OPENSSL_LIBRARIES=${LWS_OPENSSL_LIBRARIES}
-DLWS_WITH_IPV6=ON
BUILD_ALWAYS TRUE
TEST_COMMAND ""
)
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -573,6 +573,8 @@ Similar to the heap profile, you only need to specify the following environment

More information about what environment variables you can configure can be found [here](https://gperftools.github.io/gperftools/cpuprofile.html)

## Additional Features

### Filtering network interfaces

This is useful to reduce candidate gathering time when it is known for certain network interfaces to not work well. A sample callback is available in `Common.c`. The `iceSetInterfaceFilterFunc` in `KvsRtcConfiguration` must be set to the required callback. In the sample, it can be done this way in `initializePeerConnection()`:
Expand Down Expand Up @@ -632,6 +634,20 @@ The SDK enables generating these stats by default. To control whether the SDK ca
`configuration.kvsRtcConfiguration.enableIceStats = FALSE`.
Disabling these stats may lead to reductions in memory use.

### Enabling dual-stack mode
To use dual-stack AWS KVS endpoints and attempt to gather IPv6 ICE candidates, set the following environment variable:
```
export KVS_DUALSTACK_ENDPOINTS=ON
```

In dual-stack mode, ICE gathering will attempt to include IPv6 candidates, but compatibility ultimately depends on the local network configuration and the capabilities of the receiving peers.


To disable dual-stack mode, unset the environment variable:
```
unset KVS_DUALSTACK_ENDPOINTS
```

## Documentation
All Public APIs are documented in our [Include.h](https://github.com/awslabs/amazon-kinesis-video-streams-webrtc-sdk-c/blob/main/src/include/com/amazonaws/kinesis/video/webrtcclient/Include.h), we also generate a [Doxygen](https://awslabs.github.io/amazon-kinesis-video-streams-webrtc-sdk-c/) each commit for easier navigation.

Expand Down
2 changes: 2 additions & 0 deletions samples/Common.c
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,8 @@ STATUS initializePeerConnection(PSampleConfiguration pSampleConfiguration, PRtcP
* It's recommended to not pass too many TURN iceServers to configuration because it will slow down ice gathering in non-trickle mode.
*/

DLOGD("TURN server %d urls: %s", j + 1, pIceConfigInfo->uris[j]);

STRNCPY(configuration.iceServers[uriCount + 1].urls, pIceConfigInfo->uris[j], MAX_ICE_CONFIG_URI_LEN);
STRNCPY(configuration.iceServers[uriCount + 1].credential, pIceConfigInfo->password, MAX_ICE_CONFIG_CREDENTIAL_LEN);
STRNCPY(configuration.iceServers[uriCount + 1].username, pIceConfigInfo->userName, MAX_ICE_CONFIG_USER_NAME_LEN);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down
126 changes: 88 additions & 38 deletions src/source/Ice/IceAgent.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -1440,6 +1441,7 @@ STATUS iceAgentSendSrflxCandidateRequest(PIceAgent pIceAgent)
PIceServer pIceServer = NULL;
PStunPacket pBindingRequest = NULL;
UINT64 checkSum = 0;
PKvsIpAddress pStunServerAddr = NULL;
CHK(pIceAgent != NULL, STATUS_NULL_ARG);

// Assume holding pIceAgent->lock
Expand All @@ -1458,18 +1460,30 @@ STATUS iceAgentSendSrflxCandidateRequest(PIceAgent pIceAgent)
switch (pCandidate->iceCandidateType) {
case ICE_CANDIDATE_TYPE_SERVER_REFLEXIVE:
pIceServer = &(pIceAgent->iceServers[pCandidate->iceServerIndex]);
if (pIceServer->ipAddress.family == pCandidate->ipAddress.family) {
// update transactionId
CHK_STATUS(
iceUtilsGenerateTransactionId(pBindingRequest->header.transactionId, ARRAY_SIZE(pBindingRequest->header.transactionId)));

transactionIdStoreInsert(pIceAgent->pStunBindingRequestTransactionIdStore, pBindingRequest->header.transactionId);
checkSum = COMPUTE_CRC32(pBindingRequest->header.transactionId, ARRAY_SIZE(pBindingRequest->header.transactionId));
CHK_STATUS(iceAgentSendStunPacket(pBindingRequest, NULL, 0, pIceAgent, pCandidate, &pIceServer->ipAddress));
if (pIceAgent->pRtcIceServerDiagnostics[pCandidate->iceServerIndex] != NULL) {
pIceAgent->pRtcIceServerDiagnostics[pCandidate->iceServerIndex]->totalRequestsSent++;
CHK_STATUS(hashTableUpsert(pIceAgent->requestTimestampDiagnostics, checkSum, GETTIME()));
}

if (pIceServer->ipAddresses.ipv4Address.family != KVS_IP_FAMILY_TYPE_NOT_SET &&
pCandidate->ipAddress.family == KVS_IP_FAMILY_TYPE_IPV4) {
pStunServerAddr = &pIceServer->ipAddresses.ipv4Address;
} else if (pIceServer->ipAddresses.ipv6Address.family != KVS_IP_FAMILY_TYPE_NOT_SET &&
pCandidate->ipAddress.family == KVS_IP_FAMILY_TYPE_IPV6) {
pStunServerAddr = &pIceServer->ipAddresses.ipv6Address;
}
CHK_ERR(pStunServerAddr != NULL, STATUS_INVALID_ARG, "No IP-family-compatible STUN server address found for candidate %s",
pCandidate->id);

// update transactionId
CHK_STATUS(
iceUtilsGenerateTransactionId(pBindingRequest->header.transactionId, ARRAY_SIZE(pBindingRequest->header.transactionId)));

transactionIdStoreInsert(pIceAgent->pStunBindingRequestTransactionIdStore, pBindingRequest->header.transactionId);
checkSum = COMPUTE_CRC32(pBindingRequest->header.transactionId, ARRAY_SIZE(pBindingRequest->header.transactionId));

DLOGD("Sending STUN binding request to STUN server: %u:%u", pStunServerAddr->address, pStunServerAddr->port);

CHK_STATUS(iceAgentSendStunPacket(pBindingRequest, NULL, 0, pIceAgent, pCandidate, &pIceServer->ipAddresses.ipv4Address));
if (pIceAgent->pRtcIceServerDiagnostics[pCandidate->iceServerIndex] != NULL) {
pIceAgent->pRtcIceServerDiagnostics[pCandidate->iceServerIndex]->totalRequestsSent++;
CHK_STATUS(hashTableUpsert(pIceAgent->requestTimestampDiagnostics, checkSum, GETTIME()));
}
break;

Expand Down Expand Up @@ -1762,7 +1776,15 @@ STATUS iceAgentInitSrflxCandidate(PIceAgent pIceAgent)
if (pCandidate->iceCandidateType == ICE_CANDIDATE_TYPE_HOST) {
for (j = 0; j < pIceAgent->iceServersCount; j++) {
pIceServer = &pIceAgent->iceServers[j];
if (!pIceServer->isTurn && pIceServer->ipAddress.family == pCandidate->ipAddress.family) {

if (pCandidate->ipAddress.family == KVS_IP_FAMILY_TYPE_NOT_SET) {
DLOGW("Skipping local host candidate %s with unset IP family for srflx candidate generation", pCandidate->id);
continue;
}

if (!pIceServer->isTurn &&
(pIceServer->ipAddresses.ipv4Address.family == pCandidate->ipAddress.family ||
pIceServer->ipAddresses.ipv6Address.family == pCandidate->ipAddress.family)) {
CHK((pNewCandidate = (PIceCandidate) MEMCALLOC(1, SIZEOF(IceCandidate))) != NULL, STATUS_NOT_ENOUGH_MEMORY);
generateJSONSafeString(pNewCandidate->id, ARRAY_SIZE(pNewCandidate->id));
pNewCandidate->isRemote = FALSE;
Expand Down Expand Up @@ -1792,20 +1814,20 @@ STATUS iceAgentInitSrflxCandidate(PIceAgent pIceAgent)
// Create and start the connection listener outside of the locks
for (j = 0; j < srflxCount; j++) {
pCandidate = srflxCandidates[j];
// TODO: IPv6 STUN is not supported at the moment. Remove this check if the support is added in the future
if (IS_IPV4_ADDR(&(pCandidate->ipAddress))) {
// open up a new socket at host candidate's ip address for server reflex candidate.
// the new port will be stored in pNewCandidate->ipAddress.port. And the Ip address will later be updated
// with the correct ip address once the STUN response is received.
CHK_STATUS(createSocketConnection(pCandidate->ipAddress.family, KVS_SOCKET_PROTOCOL_UDP, &pCandidate->ipAddress, NULL, (UINT64) pIceAgent,
incomingDataHandler, pIceAgent->kvsRtcConfiguration.sendBufSize, &pCandidate->pSocketConnection));
ATOMIC_STORE_BOOL(&pCandidate->pSocketConnection->receiveData, TRUE);
// connectionListener will free the pSocketConnection at the end.
CHK_STATUS(connectionListenerAddConnection(pIceAgent->pConnectionListener, pCandidate->pSocketConnection));

DLOGI("Initializing an IPv4 STUN candidate...");
} else {
DLOGW("IPv6 candidate detected, ignoring....");
DLOGI("Initializing an IPv6 STUN candidate...");
}

// open up a new socket at host candidate's ip address for server reflex candidate.
// the new port will be stored in pNewCandidate->ipAddress.port. And the Ip address will later be updated
// with the correct ip address once the STUN response is received.
CHK_STATUS(createSocketConnection(pCandidate->ipAddress.family, KVS_SOCKET_PROTOCOL_UDP, &pCandidate->ipAddress, NULL, (UINT64) pIceAgent,
incomingDataHandler, pIceAgent->kvsRtcConfiguration.sendBufSize, &pCandidate->pSocketConnection));
ATOMIC_STORE_BOOL(&pCandidate->pSocketConnection->receiveData, TRUE);
// connectionListener will free the pSocketConnection at the end.
CHK_STATUS(connectionListenerAddConnection(pIceAgent->pConnectionListener, pCandidate->pSocketConnection));
}

CleanUp:
Expand Down Expand Up @@ -1835,12 +1857,27 @@ STATUS iceAgentInitRelayCandidates(PIceAgent pIceAgent)
CHK(pIceAgent != NULL, STATUS_NULL_ARG);
for (j = 0; j < pIceAgent->iceServersCount; j++) {
if (pIceAgent->iceServers[j].isTurn) {
if (pIceAgent->iceServers[j].transport == KVS_SOCKET_PROTOCOL_UDP || pIceAgent->iceServers[j].transport == KVS_SOCKET_PROTOCOL_NONE) {
CHK_STATUS(iceAgentInitRelayCandidate(pIceAgent, j, KVS_SOCKET_PROTOCOL_UDP));
DLOGD("Initializing TURN relay candidates for ICE server %u with IPv4 family %u and IPv6 family (if available) %u", j,
pIceAgent->iceServers[j].ipAddresses.ipv4Address.family, pIceAgent->iceServers[j].ipAddresses.ipv6Address.family);

if (pIceAgent->iceServers[j].ipAddresses.ipv4Address.family != KVS_IP_FAMILY_TYPE_NOT_SET) {
if (pIceAgent->iceServers[j].transport == KVS_SOCKET_PROTOCOL_UDP || pIceAgent->iceServers[j].transport == KVS_SOCKET_PROTOCOL_NONE) {
CHK_STATUS(iceAgentInitRelayCandidate(pIceAgent, j, KVS_SOCKET_PROTOCOL_UDP, KVS_IP_FAMILY_TYPE_IPV4));
}

if (pIceAgent->iceServers[j].transport == KVS_SOCKET_PROTOCOL_TCP || pIceAgent->iceServers[j].transport == KVS_SOCKET_PROTOCOL_NONE) {
CHK_STATUS(iceAgentInitRelayCandidate(pIceAgent, j, KVS_SOCKET_PROTOCOL_TCP, KVS_IP_FAMILY_TYPE_IPV4));
}
}

if (pIceAgent->iceServers[j].transport == KVS_SOCKET_PROTOCOL_TCP || pIceAgent->iceServers[j].transport == KVS_SOCKET_PROTOCOL_NONE) {
CHK_STATUS(iceAgentInitRelayCandidate(pIceAgent, j, KVS_SOCKET_PROTOCOL_TCP));
if (pIceAgent->iceServers[j].ipAddresses.ipv6Address.family != KVS_IP_FAMILY_TYPE_NOT_SET) {
if (pIceAgent->iceServers[j].transport == KVS_SOCKET_PROTOCOL_UDP || pIceAgent->iceServers[j].transport == KVS_SOCKET_PROTOCOL_NONE) {
CHK_STATUS(iceAgentInitRelayCandidate(pIceAgent, j, KVS_SOCKET_PROTOCOL_UDP, KVS_IP_FAMILY_TYPE_IPV6));
}

if (pIceAgent->iceServers[j].transport == KVS_SOCKET_PROTOCOL_TCP || pIceAgent->iceServers[j].transport == KVS_SOCKET_PROTOCOL_NONE) {
CHK_STATUS(iceAgentInitRelayCandidate(pIceAgent, j, KVS_SOCKET_PROTOCOL_TCP, KVS_IP_FAMILY_TYPE_IPV6));
}
}
}
}
Expand Down Expand Up @@ -1875,16 +1912,19 @@ 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;
UINT64 data;
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,
Expand All @@ -1895,12 +1935,17 @@ STATUS iceAgentInitRelayCandidate(PIceAgent pIceAgent, UINT32 iceServerIndex, KV
generateJSONSafeString(pNewCandidate->id, ARRAY_SIZE(pNewCandidate->id));
pNewCandidate->isRemote = FALSE;

if (turnServerIpFamily == KVS_IP_FAMILY_TYPE_IPV4) {
pTurnServerAddress = &pIceAgent->iceServers[iceServerIndex].ipAddresses.ipv4Address;
} else {
pTurnServerAddress = &pIceAgent->iceServers[iceServerIndex].ipAddresses.ipv6Address;
}

// open up a new socket without binding to any host address. The candidate Ip address will later be updated
// with the correct relay ip address once the Allocation success response is received. Relay candidate's socket is managed
// by TurnConnection struct.
CHK_STATUS(createSocketConnection(KVS_IP_FAMILY_TYPE_IPV4, protocol, NULL, &pIceAgent->iceServers[iceServerIndex].ipAddress,
(UINT64) pNewCandidate, incomingRelayedDataHandler, pIceAgent->kvsRtcConfiguration.sendBufSize,
&pNewCandidate->pSocketConnection));
CHK_STATUS(createSocketConnection(turnServerIpFamily, protocol, NULL, pTurnServerAddress, (UINT64) pNewCandidate, incomingRelayedDataHandler,
pIceAgent->kvsRtcConfiguration.sendBufSize, &pNewCandidate->pSocketConnection));
// connectionListener will free the pSocketConnection at the end.
CHK_STATUS(connectionListenerAddConnection(pIceAgent->pConnectionListener, pNewCandidate->pSocketConnection));

Expand All @@ -1917,7 +1962,7 @@ STATUS iceAgentInitRelayCandidate(PIceAgent pIceAgent, UINT32 iceServerIndex, KV

CHK_STATUS(createTurnConnection(&pIceAgent->iceServers[iceServerIndex], pIceAgent->timerQueueHandle,
TURN_CONNECTION_DATA_TRANSFER_MODE_SEND_INDIDATION, protocol, &callback, pNewCandidate->pSocketConnection,
pIceAgent->pConnectionListener, &pTurnConnection));
pIceAgent->pConnectionListener, turnServerIpFamily, &pTurnConnection));
pNewCandidate->pIceAgent = pIceAgent;
pNewCandidate->pTurnConnection = pTurnConnection;

Expand All @@ -1935,10 +1980,15 @@ STATUS iceAgentInitRelayCandidate(PIceAgent pIceAgent, UINT32 iceServerIndex, KV
pCurNode = pCurNode->pNext;
pCandidate = (PIceCandidate) data;

// TODO: Stop skipping IPv6. Since we're allowing IPv6 remote candidates from iceAgentAddRemoteCandidate for host candidates,
// it's possible to have a situation where the turn server uses IPv4 and the remote candidate uses IPv6.
if (IS_IPV4_ADDR(&pCandidate->ipAddress)) {
CHK_STATUS(turnConnectionAddPeer(pTurnConnection, &pCandidate->ipAddress));
// Add only peers with matching IP family to the TURN connection.
if (turnServerIpFamily == KVS_IP_FAMILY_TYPE_IPV4) {
if (IS_IPV4_ADDR(&pCandidate->ipAddress)) {
CHK_STATUS(turnConnectionAddPeer(pTurnConnection, &pCandidate->ipAddress));
}
} else {
if (IS_IPV6_ADDR(&pCandidate->ipAddress)) {
CHK_STATUS(turnConnectionAddPeer(pTurnConnection, &pCandidate->ipAddress));
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/source/Ice/IceAgent.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Loading
Loading