Skip to content

Commit

Permalink
Add server side ICE consent checks to detect silent WebRTC disconnect…
Browse files Browse the repository at this point in the history
…ion (#1332)

Fixes #1127
  • Loading branch information
ibc authored Feb 20, 2024
1 parent a1cc550 commit 32e83e3
Show file tree
Hide file tree
Showing 18 changed files with 667 additions and 348 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

### Next

- Add server side ICE consent checks to detect silent WebRTC disconnections ([PR #1332](https://github.com/versatica/mediasoup/pull/1332)).
- Fix regression (crash) in transport-cc feedback generation ([PR #1339](https://github.com/versatica/mediasoup/pull/1339)).

### 3.13.19
Expand Down
4 changes: 3 additions & 1 deletion node/src/Router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,7 @@ export class Router<
numSctpStreams = { OS: 1024, MIS: 1024 },
maxSctpMessageSize = 262144,
sctpSendBufferSize = 262144,
iceConsentTimeout = 30,
appData,
}: WebRtcTransportOptions<WebRtcTransportAppData>): Promise<
WebRtcTransport<WebRtcTransportAppData>
Expand Down Expand Up @@ -596,7 +597,8 @@ export class Router<
enableUdp,
enableTcp,
preferUdp,
preferTcp
preferTcp,
iceConsentTimeout
);

const requestOffset = new FbsRouter.CreateWebRtcTransportRequestT(
Expand Down
6 changes: 5 additions & 1 deletion node/src/WebRtcTransport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,11 @@ export type WebRtcTransportOptionsBase<WebRtcTransportAppData> = {
*/
preferTcp?: boolean;

/**
* ICE consent timeout (in seconds). If 0 it is disabled. Default 30.
*/
iceConsentTimeout?: number;

/**
* Initial available outgoing bitrate (in bps). Default 600000.
*/
Expand Down Expand Up @@ -836,7 +841,6 @@ function createConnectRequest({
// Serialize DtlsParameters. This can throw.
const dtlsParametersOffset = serializeDtlsParameters(builder, dtlsParameters);

// Create request.
return FbsWebRtcTransport.ConnectRequest.createConnectRequest(
builder,
dtlsParametersOffset
Expand Down
8 changes: 6 additions & 2 deletions node/src/test/test-WebRtcTransport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -299,12 +299,16 @@ test('webRtcTransport.connect() succeeds', async () => {
};

await expect(
webRtcTransport.connect({ dtlsParameters: dtlsRemoteParameters })
webRtcTransport.connect({
dtlsParameters: dtlsRemoteParameters,
})
).resolves.toBeUndefined();

// Must fail if connected.
await expect(
webRtcTransport.connect({ dtlsParameters: dtlsRemoteParameters })
webRtcTransport.connect({
dtlsParameters: dtlsRemoteParameters,
})
).rejects.toThrow(Error);

expect(webRtcTransport.dtlsParameters.role).toBe('server');
Expand Down
3 changes: 3 additions & 0 deletions rust/src/messages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -669,6 +669,7 @@ pub(crate) struct RouterCreateWebrtcTransportData {
enable_tcp: bool,
prefer_udp: bool,
prefer_tcp: bool,
ice_consent_timeout: u8,
enable_sctp: bool,
num_sctp_streams: NumSctpStreams,
max_sctp_message_size: u32,
Expand Down Expand Up @@ -701,6 +702,7 @@ impl RouterCreateWebrtcTransportData {
enable_tcp: webrtc_transport_options.enable_tcp,
prefer_udp: webrtc_transport_options.prefer_udp,
prefer_tcp: webrtc_transport_options.prefer_tcp,
ice_consent_timeout: webrtc_transport_options.ice_consent_timeout,
enable_sctp: webrtc_transport_options.enable_sctp,
num_sctp_streams: webrtc_transport_options.num_sctp_streams,
max_sctp_message_size: webrtc_transport_options.max_sctp_message_size,
Expand All @@ -726,6 +728,7 @@ impl RouterCreateWebrtcTransportData {
enable_tcp: self.enable_tcp,
prefer_udp: self.prefer_udp,
prefer_tcp: self.prefer_tcp,
ice_consent_timeout: self.ice_consent_timeout,
}
}
}
Expand Down
5 changes: 5 additions & 0 deletions rust/src/router/webrtc_transport.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,9 @@ pub struct WebRtcTransportOptions {
/// Prefer TCP.
/// Default false.
pub prefer_tcp: bool,
/// ICE consent timeout (in seconds). If 0 it is disabled.
/// Default 30.
pub ice_consent_timeout: u8,
/// Create a SCTP association.
/// Default false.
pub enable_sctp: bool,
Expand All @@ -155,6 +158,7 @@ impl WebRtcTransportOptions {
enable_tcp: false,
prefer_udp: false,
prefer_tcp: false,
ice_consent_timeout: 30,
enable_sctp: false,
num_sctp_streams: NumSctpStreams::default(),
max_sctp_message_size: 262_144,
Expand All @@ -172,6 +176,7 @@ impl WebRtcTransportOptions {
enable_tcp: true,
prefer_udp: false,
prefer_tcp: false,
ice_consent_timeout: 30,
enable_sctp: false,
num_sctp_streams: NumSctpStreams::default(),
max_sctp_message_size: 262_144,
Expand Down
4 changes: 2 additions & 2 deletions worker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ ENV LANG="C.UTF-8"
ENV CC="clang"
ENV CXX="clang++"

ENV MEDIASOUP_LOCAL_DEV=true
ENV KEEP_BUILD_ARTIFACTS=1
ENV MEDIASOUP_LOCAL_DEV="true"
ENV KEEP_BUILD_ARTIFACTS="1"

WORKDIR /mediasoup

Expand Down
4 changes: 2 additions & 2 deletions worker/Dockerfile.alpine
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ ENV LANG="C.UTF-8"
ENV CC="gcc"
ENV CXX="g++"

ENV MEDIASOUP_LOCAL_DEV=true
ENV KEEP_BUILD_ARTIFACTS=1
ENV MEDIASOUP_LOCAL_DEV="true"
ENV KEEP_BUILD_ARTIFACTS="1"

WORKDIR /mediasoup

Expand Down
1 change: 1 addition & 0 deletions worker/fbs/webRtcTransport.fbs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ table WebRtcTransportOptions {
enable_tcp: bool = true;
prefer_udp: bool = false;
prefer_tcp: bool = false;
ice_consent_timeout: uint8 = 30;
}

enum FingerprintAlgorithm: uint8 {
Expand Down
26 changes: 19 additions & 7 deletions worker/fuzzer/src/RTC/FuzzerStunPacket.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
#include "RTC/FuzzerStunPacket.hpp"
#include "RTC/StunPacket.hpp"

static constexpr size_t StunSerializeBufferSize{ 65536 };
thread_local static uint8_t StunSerializeBuffer[StunSerializeBufferSize];

void Fuzzer::RTC::StunPacket::Fuzz(const uint8_t* data, size_t len)
{
if (!::RTC::StunPacket::IsStun(data, len))
Expand All @@ -21,6 +24,7 @@ void Fuzzer::RTC::StunPacket::Fuzz(const uint8_t* data, size_t len)
packet->GetData();
packet->GetSize();
packet->SetUsername("foo", 3);
packet->SetPassword("lalala");
packet->SetPriority(123);
packet->SetIceControlling(123);
packet->SetIceControlled(123);
Expand All @@ -37,13 +41,21 @@ void Fuzzer::RTC::StunPacket::Fuzz(const uint8_t* data, size_t len)
packet->GetErrorCode();
packet->HasMessageIntegrity();
packet->HasFingerprint();
packet->CheckAuthentication("foo", "bar");
// TODO: packet->CreateSuccessResponse(); // This cannot be easily tested.
// TODO: packet->CreateErrorResponse(); // This cannot be easily tested.
packet->Authenticate("lalala");
// TODO: Cannot test Serialize() because we don't know the exact required
// buffer size (setters above may change the total size).
// TODO: packet->Serialize();
packet->CheckAuthentication("foo", "xxx");

if (packet->GetClass() == ::RTC::StunPacket::Class::REQUEST)
{
auto* successResponse = packet->CreateSuccessResponse();
auto* errorResponse = packet->CreateErrorResponse(444);

delete successResponse;
delete errorResponse;
}

if (len < StunSerializeBufferSize - 1000)
{
packet->Serialize(StunSerializeBuffer);
}

delete packet;
}
58 changes: 32 additions & 26 deletions worker/include/RTC/IceServer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,17 @@
#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 <list>
#include <string>

namespace RTC
{
class IceServer
class IceServer : public TimerHandle::Listener
{
public:
enum class IceState
Expand Down Expand Up @@ -53,8 +55,12 @@ namespace RTC
};

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

public:
void ProcessStunPacket(RTC::StunPacket* packet, RTC::TransportTuple* tuple);
Expand All @@ -74,28 +80,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 @@ -105,6 +90,9 @@ namespace RTC
void MayForceSelectedTuple(const RTC::TransportTuple* tuple);

private:
void ProcessStunRequest(RTC::StunPacket* request, RTC::TransportTuple* tuple);
void ProcessStunIndication(RTC::StunPacket* indication);
void ProcessStunResponse(RTC::StunPacket* response);
void HandleTuple(
RTC::TransportTuple* tuple, bool hasUseCandidate, bool hasNomination, uint32_t nomination);
/**
Expand All @@ -120,19 +108,37 @@ namespace RTC
* NOTE: The given tuple MUST be already stored within the list.
*/
void SetSelectedTuple(RTC::TransportTuple* storedTuple);
bool IsConsentCheckSupported() const
{
return this->consentTimeoutMs != 0u;
}
bool IsConsentCheckRunning() const
{
return (this->consentCheckTimer && this->consentCheckTimer->IsActive());
}
void StartConsentCheck();
void RestartConsentCheck();
void StopConsentCheck();

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

private:
// Passed by argument.
Listener* listener{ nullptr };
// Others.
std::string usernameFragment;
std::string password;
uint16_t consentTimeoutMs{ 30000u };
// Others.
std::string oldUsernameFragment;
std::string oldPassword;
IceState state{ IceState::NEW };
uint32_t remoteNomination{ 0u };
std::list<RTC::TransportTuple> tuples;
RTC::TransportTuple* selectedTuple{ nullptr };
TimerHandle* consentCheckTimer{ nullptr };
uint64_t lastConsentRequestReceivedAtMs{ 0u };
};
} // namespace RTC

Expand Down
25 changes: 20 additions & 5 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 All @@ -139,6 +147,7 @@ namespace RTC
{
return this->username;
}
void SetPassword(const std::string& password);
uint32_t GetPriority() const
{
return this->priority;
Expand Down Expand Up @@ -171,6 +180,10 @@ namespace RTC
{
return this->errorCode;
}
std::string GetSoftware() const
{
return this->software;
}
bool HasMessageIntegrity() const
{
return (this->messageIntegrity != nullptr);
Expand All @@ -180,10 +193,11 @@ namespace RTC
return this->hasFingerprint;
}
Authentication CheckAuthentication(
const std::string& localUsername, const std::string& localPassword);
// The first username fragment in the USERNAME attribute.
const std::string& usernameFragment1,
const std::string& password);
StunPacket* CreateSuccessResponse();
StunPacket* CreateErrorResponse(uint16_t errorCode);
void Authenticate(const std::string& password);
void Serialize(uint8_t* buffer);

private:
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 32e83e3

Please sign in to comment.