Skip to content

Commit

Permalink
Add server side ICE consent checks to detect silent WebRTC disconnection
Browse files Browse the repository at this point in the history
- Fixes #1127

### Details

- This PR provides the app with the ability to detect downed networks or silent/abrupts disconnections in client side.
- Make mediasoup send ICE consent requests every ~5 seconds once ICE is established.
- Move to 'disconnected' state (and notify the app as usual) if no ICE response is received in 30 seconds as per spec (this may change with a setting, see TODO below).
- This feature requires providing the mediasoup `WebRtcTransport` with the remote ICE username fragment and ICE password, which will be done by latest mediasoup-client once released.

### TODO

- [ ] Add a `iceConsentTimeout` parameter to `WebRtcTransportOptions` to override the default value of 30 seconds (0 means "disable the mechanism").
- [ ] Rust.
- [ ] React to 403 ICE error responses by also moving to 'disconnected' state.
  • Loading branch information
ibc committed Feb 15, 2024
1 parent 595bffe commit 57c756c
Show file tree
Hide file tree
Showing 10 changed files with 537 additions and 109 deletions.
9 changes: 8 additions & 1 deletion node/src/WebRtcTransport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,8 @@ export type IceCandidate = {
export type DtlsParameters = {
role?: DtlsRole;
fingerprints: DtlsFingerprint[];
iceUfrag?: string;
icePwd?: string;
};

/**
Expand Down Expand Up @@ -933,6 +935,9 @@ function serializeDtlsParameters(
fingerprints
);

const iceUfragOffset = builder.createString(dtlsParameters.iceUfrag ?? '');
const icePwdOffset = builder.createString(dtlsParameters.icePwd ?? '');

const role =
dtlsParameters.role !== undefined
? dtlsRoleToFbs(dtlsParameters.role)
Expand All @@ -941,6 +946,8 @@ function serializeDtlsParameters(
return FbsWebRtcTransport.DtlsParameters.createDtlsParameters(
builder,
fingerprintsOffset,
role
role,
iceUfragOffset,
icePwdOffset
);
}
2 changes: 2 additions & 0 deletions worker/fbs/webRtcTransport.fbs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ enum DtlsState: uint8 {
table DtlsParameters {
fingerprints: [Fingerprint] (required);
role: DtlsRole = AUTO;
ice_ufrag: string;
ice_pwd: string;
}

table IceParameters {
Expand Down
85 changes: 59 additions & 26 deletions worker/include/RTC/IceServer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,18 @@
#define MS_RTC_ICE_SERVER_HPP

#include "common.hpp"
#include "Utils.hpp"
#include "FBS/webRtcTransport.h"
#include "RTC/StunPacket.hpp"
#include "RTC/TransportTuple.hpp"
#include "handles/TimerHandle.hpp"
#include <deque>
#include <list>
#include <string>

namespace RTC
{
class IceServer
class IceServer : public TimerHandle::Listener
{
public:
enum class IceState
Expand All @@ -21,6 +24,19 @@ namespace RTC
DISCONNECTED,
};

public:
struct SentConsent
{
uint8_t transactionId[12];
uint64_t sentAtMs;

SentConsent(uint64_t transactionId, uint64_t sentAtMs) : sentAtMs(sentAtMs)
{
Utils::Byte::Set4Bytes(this->transactionId, 0, transactionId);
Utils::Byte::Set8Bytes(this->transactionId, 4, 0);
}
};

public:
static IceState RoleFromFbs(FBS::WebRtcTransport::IceState state);
static FBS::WebRtcTransport::IceState IceStateToFbs(IceState state);
Expand Down Expand Up @@ -54,7 +70,7 @@ namespace RTC

public:
IceServer(Listener* listener, const std::string& usernameFragment, const std::string& password);
~IceServer();
~IceServer() override;

public:
void ProcessStunPacket(RTC::StunPacket* packet, RTC::TransportTuple* tuple);
Expand All @@ -66,6 +82,21 @@ namespace RTC
{
return this->password;
}
void SetRemoteUsernameFragmentAndPassword(
const std::string& usernameFragment, const std::string& password)
{
this->remoteUsernameFragment = usernameFragment;
this->remotePassword = password;

if (AreConsentChecksEnabled())
{
MayRestartConsentChecks();
}
else
{
MayStopConsentChecks();
}
}
IceState GetState() const
{
return this->state;
Expand All @@ -74,28 +105,7 @@ namespace RTC
{
return this->selectedTuple;
}
void RestartIce(const std::string& usernameFragment, const std::string& password)
{
if (!this->oldUsernameFragment.empty())
{
this->listener->OnIceServerLocalUsernameFragmentRemoved(this, this->oldUsernameFragment);
}

this->oldUsernameFragment = this->usernameFragment;
this->usernameFragment = usernameFragment;

this->oldPassword = this->password;
this->password = password;

this->remoteNomination = 0u;

// Notify the listener.
this->listener->OnIceServerLocalUsernameFragmentAdded(this, usernameFragment);

// NOTE: Do not call listener->OnIceServerLocalUsernameFragmentRemoved()
// yet with old usernameFragment. Wait until we receive a STUN packet
// with the new one.
}
void RestartIce(const std::string& usernameFragment, const std::string& password);
bool IsValidTuple(const RTC::TransportTuple* tuple) const;
void RemoveTuple(RTC::TransportTuple* tuple);
/**
Expand All @@ -116,10 +126,29 @@ namespace RTC
*/
RTC::TransportTuple* HasTuple(const RTC::TransportTuple* tuple) const;
/**
* Set the given tuple as the selected tuple.
* Set the given tuple as the selected tuple. Returns true if given tuple
* was not already the selected tuple, false otherwise.
* NOTE: The given tuple MUST be already stored within the list.
*/
void SetSelectedTuple(RTC::TransportTuple* storedTuple);
bool SetSelectedTuple(RTC::TransportTuple* storedTuple);
bool AreConsentChecksEnabled() const
{
// TODO: We also need an API setting probably.
return (!this->remoteUsernameFragment.empty() && !this->remotePassword.empty());
}
bool AreConsentChecksRunning() const
{
return (this->consentCheckTimer && this->consentCheckTimer->IsActive());
}
void MayStartConsentChecks();
void MayStopConsentChecks();
void MayRestartConsentChecks();
void MayStartOrRestartConsentChecks();
void SendConsentRequest();

/* Pure virtual methods inherited from TimerHandle::Listener. */
public:
void OnTimer(TimerHandle* timer) override;

private:
// Passed by argument.
Expand All @@ -129,10 +158,14 @@ namespace RTC
std::string password;
std::string oldUsernameFragment;
std::string oldPassword;
std::string remoteUsernameFragment;
std::string remotePassword;
IceState state{ IceState::NEW };
uint32_t remoteNomination{ 0u };
std::list<RTC::TransportTuple> tuples;
RTC::TransportTuple* selectedTuple{ nullptr };
TimerHandle* consentCheckTimer{ nullptr };
std::deque<SentConsent> sentConsents;
};
} // namespace RTC

Expand Down
23 changes: 19 additions & 4 deletions worker/include/RTC/StunPacket.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ namespace RTC
{
OK = 0,
UNAUTHORIZED = 1,
BAD_REQUEST = 2
BAD_MESSAGE = 2
};

public:
Expand Down Expand Up @@ -95,6 +95,10 @@ namespace RTC
{
return this->size;
}
const uint8_t* GetTransactionId() const
{
return this->transactionId;
}
void SetUsername(const char* username, size_t len)
{
this->username.assign(username, len);
Expand Down Expand Up @@ -127,6 +131,10 @@ namespace RTC
{
this->errorCode = errorCode;
}
void SetSoftware(const char* software, size_t len)
{
this->software.assign(software, len);
}
void SetMessageIntegrity(const uint8_t* messageIntegrity)
{
this->messageIntegrity = messageIntegrity;
Expand Down Expand Up @@ -171,6 +179,10 @@ namespace RTC
{
return this->errorCode;
}
std::string GetSoftware() const
{
return this->software;
}
bool HasMessageIntegrity() const
{
return (this->messageIntegrity != nullptr);
Expand All @@ -180,7 +192,9 @@ namespace RTC
return this->hasFingerprint;
}
Authentication CheckAuthentication(
const std::string& localUsername, const std::string& localPassword);
const std::string& usernameFragment1,
const std::string& usernameFragment2,
const std::string& password);
StunPacket* CreateSuccessResponse();
StunPacket* CreateErrorResponse(uint16_t errorCode);
void Authenticate(const std::string& password);
Expand All @@ -194,7 +208,8 @@ namespace RTC
uint8_t* data{ nullptr }; // Pointer to binary data.
size_t size{ 0u }; // The full message size (including header).
// STUN attributes.
std::string username; // Less than 513 bytes.
std::string username; // Less than 513 bytes.
std::string password;
uint32_t priority{ 0u }; // 4 bytes unsigned integer.
uint64_t iceControlling{ 0u }; // 8 bytes unsigned integer.
uint64_t iceControlled{ 0u }; // 8 bytes unsigned integer.
Expand All @@ -205,7 +220,7 @@ namespace RTC
bool hasFingerprint{ false }; // 4 bytes.
const struct sockaddr* xorMappedAddress{ nullptr }; // 8 or 20 bytes.
uint16_t errorCode{ 0u }; // 4 bytes (no reason phrase).
std::string password;
std::string software; // Less than 763 bytes.
};
} // namespace RTC

Expand Down
Loading

0 comments on commit 57c756c

Please sign in to comment.