diff --git a/CHANGELOG.md b/CHANGELOG.md index 45b24b01b4..f90f3dd1fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/node/src/Router.ts b/node/src/Router.ts index 7b87818bf7..56245fe877 100644 --- a/node/src/Router.ts +++ b/node/src/Router.ts @@ -454,6 +454,7 @@ export class Router< numSctpStreams = { OS: 1024, MIS: 1024 }, maxSctpMessageSize = 262144, sctpSendBufferSize = 262144, + iceConsentTimeout = 30, appData, }: WebRtcTransportOptions): Promise< WebRtcTransport @@ -596,7 +597,8 @@ export class Router< enableUdp, enableTcp, preferUdp, - preferTcp + preferTcp, + iceConsentTimeout ); const requestOffset = new FbsRouter.CreateWebRtcTransportRequestT( diff --git a/node/src/WebRtcTransport.ts b/node/src/WebRtcTransport.ts index bdfb534e59..1e0a8d5b49 100644 --- a/node/src/WebRtcTransport.ts +++ b/node/src/WebRtcTransport.ts @@ -96,6 +96,11 @@ export type WebRtcTransportOptionsBase = { */ preferTcp?: boolean; + /** + * ICE consent timeout (in seconds). If 0 it is disabled. Default 30. + */ + iceConsentTimeout?: number; + /** * Initial available outgoing bitrate (in bps). Default 600000. */ @@ -836,7 +841,6 @@ function createConnectRequest({ // Serialize DtlsParameters. This can throw. const dtlsParametersOffset = serializeDtlsParameters(builder, dtlsParameters); - // Create request. return FbsWebRtcTransport.ConnectRequest.createConnectRequest( builder, dtlsParametersOffset diff --git a/node/src/test/test-WebRtcTransport.ts b/node/src/test/test-WebRtcTransport.ts index 72bfd48e4a..c605c05cb5 100644 --- a/node/src/test/test-WebRtcTransport.ts +++ b/node/src/test/test-WebRtcTransport.ts @@ -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'); diff --git a/rust/src/messages.rs b/rust/src/messages.rs index c05eed1e08..d06fcaaf52 100644 --- a/rust/src/messages.rs +++ b/rust/src/messages.rs @@ -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, @@ -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, @@ -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, } } } diff --git a/rust/src/router/webrtc_transport.rs b/rust/src/router/webrtc_transport.rs index a50a9dfa31..e3f8c3babe 100644 --- a/rust/src/router/webrtc_transport.rs +++ b/rust/src/router/webrtc_transport.rs @@ -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, @@ -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, @@ -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, diff --git a/worker/Dockerfile b/worker/Dockerfile index 37ab88d260..64ae430df2 100644 --- a/worker/Dockerfile +++ b/worker/Dockerfile @@ -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 diff --git a/worker/Dockerfile.alpine b/worker/Dockerfile.alpine index bde3577dcc..68cf11664d 100644 --- a/worker/Dockerfile.alpine +++ b/worker/Dockerfile.alpine @@ -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 diff --git a/worker/fbs/webRtcTransport.fbs b/worker/fbs/webRtcTransport.fbs index 4c12fb968c..4d66c7a511 100644 --- a/worker/fbs/webRtcTransport.fbs +++ b/worker/fbs/webRtcTransport.fbs @@ -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 { diff --git a/worker/fuzzer/src/RTC/FuzzerStunPacket.cpp b/worker/fuzzer/src/RTC/FuzzerStunPacket.cpp index 2110f8f0e1..37d567778d 100644 --- a/worker/fuzzer/src/RTC/FuzzerStunPacket.cpp +++ b/worker/fuzzer/src/RTC/FuzzerStunPacket.cpp @@ -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)) @@ -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); @@ -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; } diff --git a/worker/include/RTC/IceServer.hpp b/worker/include/RTC/IceServer.hpp index bde22a4642..72d7710363 100644 --- a/worker/include/RTC/IceServer.hpp +++ b/worker/include/RTC/IceServer.hpp @@ -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 #include namespace RTC { - class IceServer + class IceServer : public TimerHandle::Listener { public: enum class IceState @@ -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); @@ -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); /** @@ -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); /** @@ -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 tuples; RTC::TransportTuple* selectedTuple{ nullptr }; + TimerHandle* consentCheckTimer{ nullptr }; + uint64_t lastConsentRequestReceivedAtMs{ 0u }; }; } // namespace RTC diff --git a/worker/include/RTC/StunPacket.hpp b/worker/include/RTC/StunPacket.hpp index ab4846e237..1d781fff43 100644 --- a/worker/include/RTC/StunPacket.hpp +++ b/worker/include/RTC/StunPacket.hpp @@ -50,7 +50,7 @@ namespace RTC { OK = 0, UNAUTHORIZED = 1, - BAD_REQUEST = 2 + BAD_MESSAGE = 2 }; public: @@ -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); @@ -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; @@ -139,6 +147,7 @@ namespace RTC { return this->username; } + void SetPassword(const std::string& password); uint32_t GetPriority() const { return this->priority; @@ -171,6 +180,10 @@ namespace RTC { return this->errorCode; } + std::string GetSoftware() const + { + return this->software; + } bool HasMessageIntegrity() const { return (this->messageIntegrity != nullptr); @@ -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: @@ -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. @@ -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 diff --git a/worker/src/RTC/IceServer.cpp b/worker/src/RTC/IceServer.cpp index e486b4629b..756cae8eb5 100644 --- a/worker/src/RTC/IceServer.cpp +++ b/worker/src/RTC/IceServer.cpp @@ -2,6 +2,7 @@ // #define MS_LOG_DEV_LEVEL 3 #include "RTC/IceServer.hpp" +#include "DepLibUV.hpp" #include "Logger.hpp" namespace RTC @@ -11,6 +12,8 @@ namespace RTC static constexpr size_t StunSerializeBufferSize{ 65536 }; thread_local static uint8_t StunSerializeBuffer[StunSerializeBufferSize]; static constexpr size_t MaxTuples{ 8 }; + static constexpr uint8_t ConsentCheckMinTimeoutSec{ 10u }; + static constexpr uint8_t ConsentCheckMaxTimeoutSec{ 60u }; /* Class methods. */ IceServer::IceState IceStateFromFbs(FBS::WebRtcTransport::IceState state) @@ -67,11 +70,40 @@ namespace RTC /* Instance methods. */ - IceServer::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) : listener(listener), usernameFragment(usernameFragment), password(password) { MS_TRACE(); + if (consentTimeoutSec == 0u) + { + // 0 means disabled so it's a valid value. + } + else if (consentTimeoutSec < ConsentCheckMinTimeoutSec) + { + MS_WARN_TAG( + ice, + "consentTimeoutSec cannot be lower than %" PRIu8 " seconds, fixing it", + ConsentCheckMinTimeoutSec); + + consentTimeoutSec = ConsentCheckMinTimeoutSec; + } + else if (consentTimeoutSec > ConsentCheckMaxTimeoutSec) + { + MS_WARN_TAG( + ice, + "consentTimeoutSec cannot be higher than %" PRIu8 " seconds, fixing it", + ConsentCheckMaxTimeoutSec); + + consentTimeoutSec = ConsentCheckMaxTimeoutSec; + } + + this->consentTimeoutMs = consentTimeoutSec * 1000; + // Notify the listener. this->listener->OnIceServerLocalUsernameFragmentAdded(this, usernameFragment); } @@ -90,6 +122,7 @@ namespace RTC this->listener->OnIceServerLocalUsernameFragmentRemoved(this, this->oldUsernameFragment); } + // Clear all tuples. for (const auto& it : this->tuples) { auto* storedTuple = const_cast(std::addressof(it)); @@ -98,296 +131,383 @@ namespace RTC this->listener->OnIceServerTupleRemoved(this, storedTuple); } + // Clear all tuples. + // NOTE: Do it after notifying the listener since the listener may need to + // use/read the tuple being removed so we cannot free it yet. this->tuples.clear(); + + // Unset selected tuple. + this->selectedTuple = nullptr; + + // Delete the ICE consent check timer. + delete this->consentCheckTimer; + this->consentCheckTimer = nullptr; } void IceServer::ProcessStunPacket(RTC::StunPacket* packet, RTC::TransportTuple* tuple) { MS_TRACE(); - // Must be a Binding method. - if (packet->GetMethod() != RTC::StunPacket::Method::BINDING) + switch (packet->GetClass()) { - if (packet->GetClass() == RTC::StunPacket::Class::REQUEST) + case RTC::StunPacket::Class::REQUEST: { - MS_WARN_TAG( - ice, - "unknown method %#.3x in STUN Request => 400", - static_cast(packet->GetMethod())); + ProcessStunRequest(packet, tuple); - // Reply 400. - RTC::StunPacket* response = packet->CreateErrorResponse(400); + break; + } - response->Serialize(StunSerializeBuffer); - this->listener->OnIceServerSendStunPacket(this, response, tuple); + case RTC::StunPacket::Class::INDICATION: + { + ProcessStunIndication(packet); - delete response; + break; } - else + + case RTC::StunPacket::Class::SUCCESS_RESPONSE: + case RTC::StunPacket::Class::ERROR_RESPONSE: { - MS_WARN_TAG( - ice, - "ignoring STUN Indication or Response with unknown method %#.3x", - static_cast(packet->GetMethod())); + ProcessStunResponse(packet); + + break; } - return; + default: + { + MS_WARN_TAG(ice, "unknown STUN class %" PRIu16 ", discarded", packet->GetClass()); + } } + } - // Must use FINGERPRINT (optional for ICE STUN indications). - if (!packet->HasFingerprint() && packet->GetClass() != RTC::StunPacket::Class::INDICATION) + void IceServer::RestartIce(const std::string& usernameFragment, const std::string& password) + { + MS_TRACE(); + + if (!this->oldUsernameFragment.empty()) { - if (packet->GetClass() == RTC::StunPacket::Class::REQUEST) - { - MS_WARN_TAG(ice, "STUN Binding Request without FINGERPRINT => 400"); + this->listener->OnIceServerLocalUsernameFragmentRemoved(this, this->oldUsernameFragment); + } - // Reply 400. - RTC::StunPacket* response = packet->CreateErrorResponse(400); + this->oldUsernameFragment = this->usernameFragment; + this->usernameFragment = usernameFragment; - response->Serialize(StunSerializeBuffer); - this->listener->OnIceServerSendStunPacket(this, response, tuple); + this->oldPassword = this->password; + this->password = password; - delete response; - } - else + 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. + + // Restart ICE consent check (if running) to give some time to the + // client to establish ICE again. + if (IsConsentCheckSupported() && IsConsentCheckRunning()) + { + RestartConsentCheck(); + } + } + + bool IceServer::IsValidTuple(const RTC::TransportTuple* tuple) const + { + MS_TRACE(); + + return HasTuple(tuple) != nullptr; + } + + void IceServer::RemoveTuple(RTC::TransportTuple* tuple) + { + MS_TRACE(); + + RTC::TransportTuple* removedTuple{ nullptr }; + + // Find the removed tuple. + auto it = this->tuples.begin(); + + for (; it != this->tuples.end(); ++it) + { + RTC::TransportTuple* storedTuple = std::addressof(*it); + + if (storedTuple->Compare(tuple)) { - MS_WARN_TAG(ice, "ignoring STUN Binding Response without FINGERPRINT"); + removedTuple = storedTuple; + + break; } + } + // If not found, ignore. + if (!removedTuple) + { return; } - switch (packet->GetClass()) - { - case RTC::StunPacket::Class::REQUEST: - { - // USERNAME, MESSAGE-INTEGRITY and PRIORITY are required. - if (!packet->HasMessageIntegrity() || (packet->GetPriority() == 0u) || packet->GetUsername().empty()) - { - MS_WARN_TAG(ice, "mising required attributes in STUN Binding Request => 400"); + // Notify the listener. + this->listener->OnIceServerTupleRemoved(this, removedTuple); - // Reply 400. - RTC::StunPacket* response = packet->CreateErrorResponse(400); + // Remove it from the list of tuples. + // NOTE: Do it after notifying the listener since the listener may need to + // use/read the tuple being removed so we cannot free it yet. + this->tuples.erase(it); - response->Serialize(StunSerializeBuffer); - this->listener->OnIceServerSendStunPacket(this, response, tuple); + // If this is the selected tuple, do things. + if (removedTuple == this->selectedTuple) + { + this->selectedTuple = nullptr; - delete response; + // Mark the first tuple as selected tuple (if any) but only if state was + // 'connected' or 'completed'. + if ( + (this->state == IceState::CONNECTED || this->state == IceState::COMPLETED) && + this->tuples.begin() != this->tuples.end()) + { + SetSelectedTuple(std::addressof(*this->tuples.begin())); - return; + // Restart ICE consent check to let the client send new consent requests + // on the new selected tuple. + if (IsConsentCheckSupported()) + { + RestartConsentCheck(); } + } + // Or just emit 'disconnected'. + else + { + // Update state. + this->state = IceState::DISCONNECTED; - // Check authentication. - switch (packet->CheckAuthentication(this->usernameFragment, this->password)) - { - case RTC::StunPacket::Authentication::OK: - { - if (!this->oldUsernameFragment.empty() && !this->oldPassword.empty()) - { - MS_DEBUG_TAG(ice, "new ICE credentials applied"); + // Reset remote nomination. + this->remoteNomination = 0u; - // Notify the listener. - this->listener->OnIceServerLocalUsernameFragmentRemoved(this, this->oldUsernameFragment); + // Notify the listener. + this->listener->OnIceServerDisconnected(this); - this->oldUsernameFragment.clear(); - this->oldPassword.clear(); - } + if (IsConsentCheckSupported() && IsConsentCheckRunning()) + { + StopConsentCheck(); + } + } + } + } - break; - } + void IceServer::ProcessStunRequest(RTC::StunPacket* request, RTC::TransportTuple* tuple) + { + MS_TRACE(); - case RTC::StunPacket::Authentication::UNAUTHORIZED: - { - // We may have changed our usernameFragment and password, so check - // the old ones. - // clang-format off - if ( - !this->oldUsernameFragment.empty() && - !this->oldPassword.empty() && - packet->CheckAuthentication(this->oldUsernameFragment, this->oldPassword) == RTC::StunPacket::Authentication::OK - ) - // clang-format on - { - MS_DEBUG_TAG(ice, "using old ICE credentials"); + MS_DEBUG_DEV("processing STUN request"); - break; - } + // Must be a Binding method. + if (request->GetMethod() != RTC::StunPacket::Method::BINDING) + { + MS_WARN_TAG( + ice, + "STUN request with unknown method %#.3x => 400", + static_cast(request->GetMethod())); - MS_WARN_TAG(ice, "wrong authentication in STUN Binding Request => 401"); + // Reply 400. + RTC::StunPacket* response = request->CreateErrorResponse(400); - // Reply 401. - RTC::StunPacket* response = packet->CreateErrorResponse(401); + response->Serialize(StunSerializeBuffer); + this->listener->OnIceServerSendStunPacket(this, response, tuple); - response->Serialize(StunSerializeBuffer); - this->listener->OnIceServerSendStunPacket(this, response, tuple); + delete response; - delete response; + return; + } - return; - } + // Must have FINGERPRINT attribute. + if (!request->HasFingerprint()) + { + MS_WARN_TAG(ice, "STUN Binding request without FINGERPRINT attribute => 400"); - case RTC::StunPacket::Authentication::BAD_REQUEST: - { - MS_WARN_TAG(ice, "cannot check authentication in STUN Binding Request => 400"); + // Reply 400. + RTC::StunPacket* response = request->CreateErrorResponse(400); - // Reply 400. - RTC::StunPacket* response = packet->CreateErrorResponse(400); + response->Serialize(StunSerializeBuffer); + this->listener->OnIceServerSendStunPacket(this, response, tuple); - response->Serialize(StunSerializeBuffer); - this->listener->OnIceServerSendStunPacket(this, response, tuple); + delete response; - delete response; + return; + } - return; - } - } + // PRIORITY attribute is required. + if (request->GetPriority() == 0u) + { + MS_WARN_TAG(ice, "STUN Binding request without PRIORITY attribute => 400"); - // The remote peer must be ICE controlling. - if (packet->GetIceControlled()) - { - MS_WARN_TAG(ice, "peer indicates ICE-CONTROLLED in STUN Binding Request => 487"); + // Reply 400. + RTC::StunPacket* response = request->CreateErrorResponse(400); - // Reply 487 (Role Conflict). - RTC::StunPacket* response = packet->CreateErrorResponse(487); + response->Serialize(StunSerializeBuffer); + this->listener->OnIceServerSendStunPacket(this, response, tuple); - response->Serialize(StunSerializeBuffer); - this->listener->OnIceServerSendStunPacket(this, response, tuple); + delete response; - delete response; + return; + } - return; - } + // Check authentication. + switch (request->CheckAuthentication(this->usernameFragment, this->password)) + { + case RTC::StunPacket::Authentication::OK: + { + if (!this->oldUsernameFragment.empty() && !this->oldPassword.empty()) + { + MS_DEBUG_TAG(ice, "new ICE credentials applied"); - MS_DEBUG_DEV( - "processing STUN Binding Request [Priority:%" PRIu32 ", UseCandidate:%s]", - static_cast(packet->GetPriority()), - packet->HasUseCandidate() ? "true" : "false"); + // Notify the listener. + this->listener->OnIceServerLocalUsernameFragmentRemoved(this, this->oldUsernameFragment); - // Create a success response. - RTC::StunPacket* response = packet->CreateSuccessResponse(); + this->oldUsernameFragment.clear(); + this->oldPassword.clear(); + } - // Add XOR-MAPPED-ADDRESS. - response->SetXorMappedAddress(tuple->GetRemoteAddress()); + break; + } - // Authenticate the response. - if (this->oldPassword.empty()) - { - response->Authenticate(this->password); - } - else + case RTC::StunPacket::Authentication::UNAUTHORIZED: + { + // We may have changed our usernameFragment and password, so check the + // old ones. + // clang-format off + if ( + !this->oldUsernameFragment.empty() && + !this->oldPassword.empty() && + request->CheckAuthentication( + this->oldUsernameFragment, this->oldPassword + ) == RTC::StunPacket::Authentication::OK + ) + // clang-format on { - response->Authenticate(this->oldPassword); + MS_DEBUG_TAG(ice, "using old ICE credentials"); + + break; } - // Send back. + MS_WARN_TAG(ice, "wrong authentication in STUN Binding request => 401"); + + // Reply 401. + RTC::StunPacket* response = request->CreateErrorResponse(401); + response->Serialize(StunSerializeBuffer); this->listener->OnIceServerSendStunPacket(this, response, tuple); delete response; - uint32_t nomination{ 0u }; - - if (packet->HasNomination()) - { - nomination = packet->GetNomination(); - } - - // Handle the tuple. - HandleTuple(tuple, packet->HasUseCandidate(), packet->HasNomination(), nomination); - - break; + return; } - case RTC::StunPacket::Class::INDICATION: + case RTC::StunPacket::Authentication::BAD_MESSAGE: { - MS_DEBUG_TAG(ice, "STUN Binding Indication processed"); + MS_WARN_TAG(ice, "cannot check authentication in STUN Binding request => 400"); - break; - } - - case RTC::StunPacket::Class::SUCCESS_RESPONSE: - { - MS_DEBUG_TAG(ice, "STUN Binding Success Response processed"); + // Reply 400. + RTC::StunPacket* response = request->CreateErrorResponse(400); - break; - } + response->Serialize(StunSerializeBuffer); + this->listener->OnIceServerSendStunPacket(this, response, tuple); - case RTC::StunPacket::Class::ERROR_RESPONSE: - { - MS_DEBUG_TAG(ice, "STUN Binding Error Response processed"); + delete response; - break; + return; } } - } - bool IceServer::IsValidTuple(const RTC::TransportTuple* tuple) const - { - MS_TRACE(); + // The remote peer must be ICE controlling. + if (request->GetIceControlled()) + { + MS_WARN_TAG(ice, "peer indicates ICE-CONTROLLED in STUN Binding request => 487"); - return HasTuple(tuple) != nullptr; - } + // Reply 487 (Role Conflict). + RTC::StunPacket* response = request->CreateErrorResponse(487); - void IceServer::RemoveTuple(RTC::TransportTuple* tuple) - { - MS_TRACE(); + response->Serialize(StunSerializeBuffer); + this->listener->OnIceServerSendStunPacket(this, response, tuple); - RTC::TransportTuple* removedTuple{ nullptr }; + delete response; - // Find the removed tuple. - auto it = this->tuples.begin(); + return; + } - for (; it != this->tuples.end(); ++it) - { - RTC::TransportTuple* storedTuple = std::addressof(*it); + MS_DEBUG_DEV( + "valid STUN Binding request [priority:%" PRIu32 ", useCandidate:%s]", + static_cast(request->GetPriority()), + request->HasUseCandidate() ? "true" : "false"); - if (storedTuple->Compare(tuple)) - { - removedTuple = storedTuple; + // Create a success response. + RTC::StunPacket* response = request->CreateSuccessResponse(); - break; - } - } + // Add XOR-MAPPED-ADDRESS. + response->SetXorMappedAddress(tuple->GetRemoteAddress()); - // If not found, ignore. - if (!removedTuple) + // Authenticate the response. + if (this->oldPassword.empty()) { - return; + response->SetPassword(this->password); + } + else + { + response->SetPassword(this->oldPassword); } - // Notify the listener. - this->listener->OnIceServerTupleRemoved(this, removedTuple); + // Send back. + response->Serialize(StunSerializeBuffer); + this->listener->OnIceServerSendStunPacket(this, response, tuple); - // Remove it from the list of tuples. - // NOTE: Do it after notifying the listener since the listener may need to - // use/read the tuple being removed so we cannot free it yet. - this->tuples.erase(it); + delete response; - // If this is the selected tuple, do things. - if (removedTuple == this->selectedTuple) + uint32_t nomination{ 0u }; + + if (request->HasNomination()) { - this->selectedTuple = nullptr; + nomination = request->GetNomination(); + } - // Mark the first tuple as selected tuple (if any). - if (this->tuples.begin() != this->tuples.end()) + // Handle the tuple. + HandleTuple(tuple, request->HasUseCandidate(), request->HasNomination(), nomination); + + // If state is 'connected' or 'completed' after handling the tuple, then + // start or restart ICE consent check (if supported). + if (IsConsentCheckSupported() && (this->state == IceState::CONNECTED || this->state == IceState::COMPLETED)) + { + if (IsConsentCheckRunning()) { - SetSelectedTuple(std::addressof(*this->tuples.begin())); + RestartConsentCheck(); } - // Or just emit 'disconnected'. else { - // Update state. - this->state = IceState::DISCONNECTED; - - // Reset remote nomination. - this->remoteNomination = 0u; - - // Notify the listener. - this->listener->OnIceServerDisconnected(this); + StartConsentCheck(); } } } + void IceServer::ProcessStunIndication(RTC::StunPacket* indication) + { + MS_TRACE(); + + MS_DEBUG_DEV("STUN indication received, discarded"); + + // Nothig else to do. We just discard STUN indications. + } + + void IceServer::ProcessStunResponse(RTC::StunPacket* response) + { + MS_TRACE(); + + const std::string responseType = response->GetClass() == RTC::StunPacket::Class::SUCCESS_RESPONSE + ? "success" + : std::to_string(response->GetErrorCode()) + " error"; + + MS_DEBUG_DEV("processing STUN %s response received, discarded", responseType.c_str()); + + // Nothig else to do. We just discard STUN responses because we do not + // generate STUN requests. + } + void IceServer::MayForceSelectedTuple(const RTC::TransportTuple* tuple) { MS_TRACE(); @@ -408,7 +528,6 @@ namespace RTC return; } - // Mark it as selected tuple. SetSelectedTuple(storedTuple); } @@ -437,12 +556,12 @@ namespace RTC // Store the tuple. auto* storedTuple = AddTuple(tuple); - // Mark it as selected tuple. - SetSelectedTuple(storedTuple); - // Update state. this->state = IceState::CONNECTED; + // Mark it as selected tuple. + SetSelectedTuple(storedTuple); + // Notify the listener. this->listener->OnIceServerConnected(this); } @@ -461,12 +580,12 @@ namespace RTC hasNomination ? "true" : "false", nomination); - // Mark it as selected tuple. - SetSelectedTuple(storedTuple); - // Update state. this->state = IceState::COMPLETED; + // Mark it as selected tuple. + SetSelectedTuple(storedTuple); + // Update nomination. if (hasNomination && nomination > this->remoteNomination) { @@ -499,12 +618,12 @@ namespace RTC // Store the tuple. auto* storedTuple = AddTuple(tuple); - // Mark it as selected tuple. - SetSelectedTuple(storedTuple); - // Update state. this->state = IceState::CONNECTED; + // Mark it as selected tuple. + SetSelectedTuple(storedTuple); + // Notify the listener. this->listener->OnIceServerConnected(this); } @@ -523,12 +642,12 @@ namespace RTC hasNomination ? "true" : "false", nomination); - // Mark it as selected tuple. - SetSelectedTuple(storedTuple); - // Update state. this->state = IceState::COMPLETED; + // Mark it as selected tuple. + SetSelectedTuple(storedTuple); + // Update nomination. if (hasNomination && nomination > this->remoteNomination) { @@ -571,12 +690,12 @@ namespace RTC if ((hasNomination && nomination > this->remoteNomination) || !hasNomination) { - // Mark it as selected tuple. - SetSelectedTuple(storedTuple); - // Update state. this->state = IceState::COMPLETED; + // Mark it as selected tuple. + SetSelectedTuple(storedTuple); + // Update nomination. if (hasNomination && nomination > this->remoteNomination) { @@ -627,7 +746,7 @@ namespace RTC } } - inline RTC::TransportTuple* IceServer::AddTuple(RTC::TransportTuple* tuple) + RTC::TransportTuple* IceServer::AddTuple(RTC::TransportTuple* tuple) { MS_TRACE(); @@ -635,7 +754,7 @@ namespace RTC if (storedTuple) { - MS_DEBUG_DEV('tuple already exists'); + MS_DEBUG_DEV("tuple already exists"); return storedTuple; } @@ -695,7 +814,7 @@ namespace RTC return storedTuple; } - inline RTC::TransportTuple* IceServer::HasTuple(const RTC::TransportTuple* tuple) const + RTC::TransportTuple* IceServer::HasTuple(const RTC::TransportTuple* tuple) const { MS_TRACE(); @@ -719,7 +838,7 @@ namespace RTC return nullptr; } - inline void IceServer::SetSelectedTuple(RTC::TransportTuple* storedTuple) + void IceServer::SetSelectedTuple(RTC::TransportTuple* storedTuple) { MS_TRACE(); @@ -734,4 +853,88 @@ namespace RTC // Notify the listener. this->listener->OnIceServerSelectedTuple(this, this->selectedTuple); } + + void IceServer::StartConsentCheck() + { + MS_TRACE(); + + MS_ASSERT(IsConsentCheckSupported(), "ICE consent check not supported"); + MS_ASSERT(!IsConsentCheckRunning(), "ICE consent check already running"); + MS_ASSERT(this->selectedTuple, "no selected tuple"); + + // Create the ICE consent check timer if it doesn't exist. + if (!this->consentCheckTimer) + { + this->consentCheckTimer = new TimerHandle(this); + } + + this->consentCheckTimer->Start(this->consentTimeoutMs); + } + + void IceServer::RestartConsentCheck() + { + MS_TRACE(); + + MS_ASSERT(IsConsentCheckSupported(), "ICE consent check not supported"); + MS_ASSERT(IsConsentCheckRunning(), "ICE consent check not running"); + MS_ASSERT(this->selectedTuple, "no selected tuple"); + + this->consentCheckTimer->Restart(); + } + + void IceServer::StopConsentCheck() + { + MS_TRACE(); + + MS_ASSERT(IsConsentCheckSupported(), "ICE consent check not supported"); + MS_ASSERT(IsConsentCheckRunning(), "ICE consent check not running"); + + this->consentCheckTimer->Stop(); + } + + inline void IceServer::OnTimer(TimerHandle* timer) + { + MS_TRACE(); + + if (timer == this->consentCheckTimer) + { + MS_ASSERT(IsConsentCheckSupported(), "ICE consent check not supported"); + + // State must be 'connected' or 'completed'. + MS_ASSERT( + this->state == IceState::COMPLETED || this->state == IceState::CONNECTED, + "ICE consent check timer fired but state is neither 'completed' nor 'connected'"); + + // There should be a selected tuple. + MS_ASSERT(this->selectedTuple, "ICE consent check timer fired but there is not selected tuple"); + + MS_WARN_TAG(ice, "ICE consent expired due to timeout, moving to 'disconnected' state"); + + // Update state. + this->state = IceState::DISCONNECTED; + + // Reset remote nomination. + this->remoteNomination = 0u; + + // Clear all tuples. + for (const auto& it : this->tuples) + { + auto* storedTuple = const_cast(std::addressof(it)); + + // Notify the listener. + this->listener->OnIceServerTupleRemoved(this, storedTuple); + } + + // Clear all tuples. + // NOTE: Do it after notifying the listener since the listener may need to + // use/read the tuple being removed so we cannot free it yet. + this->tuples.clear(); + + // Unset selected tuple. + this->selectedTuple = nullptr; + + // Notify the listener. + this->listener->OnIceServerDisconnected(this); + } + } } // namespace RTC diff --git a/worker/src/RTC/KeyFrameRequestManager.cpp b/worker/src/RTC/KeyFrameRequestManager.cpp index 1112901b49..540414ae0c 100644 --- a/worker/src/RTC/KeyFrameRequestManager.cpp +++ b/worker/src/RTC/KeyFrameRequestManager.cpp @@ -4,7 +4,7 @@ #include "RTC/KeyFrameRequestManager.hpp" #include "Logger.hpp" -static constexpr uint32_t KeyFrameRetransmissionWaitTime{ 1000 }; +static constexpr uint32_t KeyFrameRetransmissionWaitTime{ 1000u }; /* PendingKeyFrameInfo methods. */ diff --git a/worker/src/RTC/StunPacket.cpp b/worker/src/RTC/StunPacket.cpp index 518ca8ff97..6747afebd1 100644 --- a/worker/src/RTC/StunPacket.cpp +++ b/worker/src/RTC/StunPacket.cpp @@ -24,23 +24,21 @@ namespace RTC return nullptr; } - /* - The message type field is decomposed further into the following - structure: - - 0 1 - 2 3 4 5 6 7 8 9 0 1 2 3 4 5 - +--+--+-+-+-+-+-+-+-+-+-+-+-+-+ - |M |M |M|M|M|C|M|M|M|C|M|M|M|M| - |11|10|9|8|7|1|6|5|4|0|3|2|1|0| - +--+--+-+-+-+-+-+-+-+-+-+-+-+-+ - - Figure 3: Format of STUN Message Type Field - - Here the bits in the message type field are shown as most significant - (M11) through least significant (M0). M11 through M0 represent a 12- - bit encoding of the method. C1 and C0 represent a 2-bit encoding of - the class. + /** + * The message type field is decomposed further into the following + * structure: + * + * 0 1 + * 2 3 4 5 6 7 8 9 0 1 2 3 4 5 + * +--+--+-+-+-+-+-+-+-+-+-+-+-+-+ + * |M |M |M|M|M|C|M|M|M|C|M|M|M|M| + * |11|10|9|8|7|1|6|5|4|0|3|2|1|0| + * +--+--+-+-+-+-+-+-+-+-+-+-+-+-+ + * + * Here the bits in the message type field are shown as most significant + * (M11) through least significant (M0). M11 through M0 represent a 12-bit + * encoding of the method. C1 and C0 represent a 2-bit encoding of the + * class. */ // Get type field. @@ -49,7 +47,8 @@ namespace RTC // Get length field. const uint16_t msgLength = Utils::Byte::Get2Bytes(data, 2); - // length field must be total size minus header's 20 bytes, and must be multiple of 4 Bytes. + // length field must be total size minus header's 20 bytes, and must be + // multiple of 4 Bytes. if ((static_cast(msgLength) != len - 20) || ((msgLength & 0x03) != 0)) { MS_WARN_TAG( @@ -67,35 +66,38 @@ namespace RTC // Get STUN class. const uint16_t msgClass = ((data[0] & 0x01) << 1) | ((data[1] & 0x10) >> 4); - // Create a new StunPacket (data + 8 points to the received TransactionID field). + // Create a new StunPacket (data + 8 points to the received TransactionID + // field). auto* packet = new StunPacket( static_cast(msgClass), static_cast(msgMethod), data + 8, data, len); - /* - STUN Attributes - - After the STUN header are zero or more attributes. Each attribute - MUST be TLV encoded, with a 16-bit type, 16-bit length, and value. - Each STUN attribute MUST end on a 32-bit boundary. As mentioned - above, all fields in an attribute are transmitted most significant - bit first. - - 0 1 2 3 - 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Type | Length | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Value (variable) .... - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + /** + * STUN Attributes + * + * After the STUN header are zero or more attributes. Each attribute MUST + * be TLV encoded, with a 16-bit type, 16-bit length, and value. Each STUN + * attribute MUST end on a 32-bit boundary. As mentioned above, all fields + * in an attribute are transmitted most significant bit first. + * + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Type | Length | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Value (variable) .... + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ // Start looking for attributes after STUN header (Byte #20). size_t pos{ 20 }; - // Flags (positions) for special MESSAGE-INTEGRITY and FINGERPRINT attributes. + // Flags (positions) for special MESSAGE-INTEGRITY and FINGERPRINT + // attributes. bool hasMessageIntegrity{ false }; bool hasFingerprint{ false }; - size_t fingerprintAttrPos; // Will point to the beginning of the attribute. - uint32_t fingerprint; // Holds the value of the FINGERPRINT attribute. + // Will point to the beginning of the attribute. + size_t fingerprintAttrPos; + // Holds the value of the FINGERPRINT attribute. + uint32_t fingerprint; // Ensure there are at least 4 remaining bytes (attribute with 0 length). while (pos + 4 <= len) @@ -285,6 +287,24 @@ namespace RTC break; } + case Attribute::SOFTWARE: + { + // Ensure attribute length is less than 763 bytes. + if (attrLength >= 763) + { + MS_WARN_TAG( + ice, "attribute SOFTWARE must be less than 763 bytes length, packet discarded"); + + delete packet; + return nullptr; + } + + packet->SetSoftware( + reinterpret_cast(attrValuePos), static_cast(attrLength)); + + break; + } + default:; } @@ -351,16 +371,16 @@ namespace RTC switch (this->klass) { case Class::REQUEST: - klass = "Request"; + klass = "request"; break; case Class::INDICATION: - klass = "Indication"; + klass = "indication"; break; case Class::SUCCESS_RESPONSE: - klass = "SuccessResponse"; + klass = "success response"; break; case Class::ERROR_RESPONSE: - klass = "ErrorResponse"; + klass = "error response"; break; } if (this->method == Method::BINDING) @@ -374,14 +394,11 @@ namespace RTC } MS_DUMP(" size: %zu bytes", this->size); - char transactionId[25]; + auto transactionId1 = Utils::Byte::Get4Bytes(this->transactionId, 0); + auto transactionId2 = Utils::Byte::Get8Bytes(this->transactionId, 4); - for (int i{ 0 }; i < 12; ++i) - { - // NOTE: n must be 3 because snprintf adds a \0 after printed chars. - std::snprintf(transactionId + (i * 2), 3, "%.2x", this->transactionId[i]); - } - MS_DUMP(" transactionId: %s", transactionId); + MS_DUMP(" transactionId (first 4 bytes): %" PRIu32, transactionId1); + MS_DUMP(" transactionId (last 8 bytes): %" PRIu64, transactionId2); if (this->errorCode != 0u) { MS_DUMP(" errorCode: %" PRIu16, this->errorCode); @@ -406,6 +423,10 @@ namespace RTC { MS_DUMP(" useCandidate"); } + if (!this->software.empty()) + { + MS_DUMP(" software: %s", this->software.c_str()); + } if (this->xorMappedAddress != nullptr) { int family; @@ -435,8 +456,21 @@ namespace RTC MS_DUMP(""); } + void StunPacket::SetPassword(const std::string& password) + { + // Just for request, indication and success response messages. + if (this->klass == Class::ERROR_RESPONSE) + { + MS_ERROR("cannot set password for error responses"); + + return; + } + + this->password = password; + } + StunPacket::Authentication StunPacket::CheckAuthentication( - const std::string& localUsername, const std::string& localPassword) + const std::string& usernameFragment1, const std::string& password) { MS_TRACE(); @@ -445,46 +479,82 @@ namespace RTC case Class::REQUEST: case Class::INDICATION: { - // Both USERNAME and MESSAGE-INTEGRITY must be present. - if (!this->messageIntegrity || this->username.empty()) + // usernameFragment1 must be given. + if (usernameFragment1.empty()) + { + MS_WARN_TAG(ice, "usernameFragment1 not given, cannot authenticate request or indication"); + + return Authentication::BAD_MESSAGE; + } + + // USERNAME attribute must be present. + if (this->username.empty()) + { + MS_WARN_TAG(ice, "missing USERNAME attribute, cannot authenticate request or indication"); + + return Authentication::BAD_MESSAGE; + } + + // MESSAGE-INTEGRITY attribute must be present. + if (!this->messageIntegrity) { - return Authentication::BAD_REQUEST; + MS_WARN_TAG( + ice, "missing MESSAGE-INTEGRITY attribute, cannot authenticate request or indication"); + + return Authentication::BAD_MESSAGE; } - // Check that USERNAME attribute begins with our local username plus ":". - const size_t localUsernameLen = localUsername.length(); + // Check that the USERNAME attribute begins with the first username + // fragment plus ":". + const size_t usernameFragment1Len = usernameFragment1.length(); if ( - this->username.length() <= localUsernameLen || this->username.at(localUsernameLen) != ':' || - (this->username.compare(0, localUsernameLen, localUsername) != 0)) + this->username.length() <= usernameFragment1Len || + this->username.at(usernameFragment1Len) != ':' || + this->username.compare(0, usernameFragment1Len, usernameFragment1) != 0) { return Authentication::UNAUTHORIZED; } break; } - // This method cannot check authentication in received responses (as we - // are ICE-Lite and don't generate requests). + case Class::SUCCESS_RESPONSE: case Class::ERROR_RESPONSE: { - MS_ERROR("cannot check authentication for a STUN response"); + // MESSAGE-INTEGRITY attribute must be present. + if (!this->messageIntegrity) + { + MS_WARN_TAG(ice, "missing MESSAGE-INTEGRITY attribute, cannot authenticate response"); - return Authentication::BAD_REQUEST; + return Authentication::BAD_MESSAGE; + } + + break; + } + + default: + { + MS_WARN_TAG(ice, "unknown STUN class %" PRIu16 ", cannot authenticate", this->klass); + + return Authentication::BAD_MESSAGE; } } - // If there is FINGERPRINT it must be discarded for MESSAGE-INTEGRITY calculation, - // so the header length field must be modified (and later restored). + // If there is FINGERPRINT it must be discarded for MESSAGE-INTEGRITY + // calculation, so the header length field must be modified (and later + // restored). if (this->hasFingerprint) { - // Set the header length field: full size - header length (20) - FINGERPRINT length (8). + // Set the header length field: full size - header length (20) - + // FINGERPRINT length (8). Utils::Byte::Set2Bytes(this->data, 2, static_cast(this->size - 20 - 8)); } - // Calculate the HMAC-SHA1 of the message according to MESSAGE-INTEGRITY rules. - const uint8_t* computedMessageIntegrity = Utils::Crypto::GetHmacSha1( - localPassword, this->data, (this->messageIntegrity - 4) - this->data); + // Calculate the HMAC-SHA1 of the message according to MESSAGE-INTEGRITY + // rules. + const uint8_t* computedMessageIntegrity = + Utils::Crypto::GetHmacSha1(password, this->data, (this->messageIntegrity - 4) - this->data); Authentication result; @@ -513,7 +583,7 @@ namespace RTC MS_ASSERT( this->klass == Class::REQUEST, - "attempt to create a success response for a non Request STUN packet"); + "attempt to create a success response for a non request STUN packet"); return new StunPacket(Class::SUCCESS_RESPONSE, this->method, this->transactionId, nullptr, 0); } @@ -524,7 +594,7 @@ namespace RTC MS_ASSERT( this->klass == Class::REQUEST, - "attempt to create an error response for a non Request STUN packet"); + "attempt to create an error response for a non request STUN packet"); auto* response = new StunPacket(Class::ERROR_RESPONSE, this->method, this->transactionId, nullptr, 0); @@ -534,19 +604,6 @@ namespace RTC return response; } - void StunPacket::Authenticate(const std::string& password) - { - // Just for Request, Indication and SuccessResponse messages. - if (this->klass == Class::ERROR_RESPONSE) - { - MS_ERROR("cannot set password for ErrorResponse messages"); - - return; - } - - this->password = password; - } - void StunPacket::Serialize(uint8_t* buffer) { MS_TRACE(); @@ -807,7 +864,8 @@ namespace RTC Utils::Byte::Set2Bytes(buffer, 2, static_cast(this->size - 20 - 8)); } - // Calculate the HMAC-SHA1 of the packet according to MESSAGE-INTEGRITY rules. + // Calculate the HMAC-SHA1 of the packet according to MESSAGE-INTEGRITY + // rules. const uint8_t* computedMessageIntegrity = Utils::Crypto::GetHmacSha1(this->password, buffer, pos); diff --git a/worker/src/RTC/Transport.cpp b/worker/src/RTC/Transport.cpp index 830b31ddb7..ea5426970f 100644 --- a/worker/src/RTC/Transport.cpp +++ b/worker/src/RTC/Transport.cpp @@ -3083,8 +3083,8 @@ namespace RTC /* * The interval between RTCP packets is varied randomly over the range - * [1.0,1.5] times the calculated interval to avoid unintended synchronization - * of all participants. + * [1.0, 1.5] times the calculated interval to avoid unintended + * synchronization of all participants. */ interval *= static_cast(Utils::Crypto::GetRandomUInt(10, 15)) / 10; diff --git a/worker/src/RTC/WebRtcServer.cpp b/worker/src/RTC/WebRtcServer.cpp index bd1c095c13..362b43d455 100644 --- a/worker/src/RTC/WebRtcServer.cpp +++ b/worker/src/RTC/WebRtcServer.cpp @@ -499,7 +499,7 @@ namespace RTC if (this->mapTupleWebRtcTransport.find(tuple->hash) == this->mapTupleWebRtcTransport.end()) { - MS_WARN_TAG(ice, "tuple hash not found in the table"); + MS_DEBUG_TAG(ice, "tuple hash not found in the table"); return; } diff --git a/worker/src/RTC/WebRtcTransport.cpp b/worker/src/RTC/WebRtcTransport.cpp index c718c1179a..a5deb26fe0 100644 --- a/worker/src/RTC/WebRtcTransport.cpp +++ b/worker/src/RTC/WebRtcTransport.cpp @@ -160,9 +160,11 @@ namespace RTC iceLocalPreferenceDecrement += 100; } + auto iceConsentTimeout = options->iceConsentTimeout(); + // Create a ICE server. this->iceServer = new RTC::IceServer( - this, Utils::Crypto::GetRandomString(32), Utils::Crypto::GetRandomString(32)); + this, Utils::Crypto::GetRandomString(32), Utils::Crypto::GetRandomString(32), iceConsentTimeout); // Create a DTLS transport. this->dtlsTransport = new RTC::DtlsTransport(this); @@ -227,9 +229,11 @@ namespace RTC MS_THROW_TYPE_ERROR("empty iceCandidates"); } + auto iceConsentTimeout = options->iceConsentTimeout(); + // Create a ICE server. this->iceServer = new RTC::IceServer( - this, Utils::Crypto::GetRandomString(32), Utils::Crypto::GetRandomString(32)); + this, Utils::Crypto::GetRandomString(32), Utils::Crypto::GetRandomString(32), iceConsentTimeout); // Create a DTLS transport. this->dtlsTransport = new RTC::DtlsTransport(this); @@ -435,7 +439,8 @@ namespace RTC MS_THROW_ERROR("connect() already called"); } - const auto* body = request->data->body_as(); + const auto* body = request->data->body_as(); + const auto* dtlsParameters = body->dtlsParameters(); RTC::DtlsTransport::Fingerprint dtlsRemoteFingerprint;