diff options
author | Florent Castelli <orphis@webrtc.org> | 2022-03-31 19:15:10 +0200 |
---|---|---|
committer | WebRTC LUCI CQ <webrtc-scoped@luci-project-accounts.iam.gserviceaccount.com> | 2022-04-04 10:30:46 +0000 |
commit | f2599a7f4374cb2d5b469c4d6d8e3250bad0a128 (patch) | |
tree | 3bbc76d3b3086d3adb3cc0338f77b18824cf8a10 /media/sctp | |
parent | c128277f5600970e9d23bb2697a65551193e32e2 (diff) | |
download | webrtc-f2599a7f4374cb2d5b469c4d6d8e3250bad0a128.tar.gz |
Remove usrsctp, dcSCTP is now the unique SCTP implementation
Bug: chromium:1243702
Change-Id: Id11299d26f0f8713a57781b57277837aace531f2
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/251821
Reviewed-by: Mirko Bonadei <mbonadei@webrtc.org>
Reviewed-by: Victor Boivie <boivie@webrtc.org>
Reviewed-by: Harald Alvestrand <hta@webrtc.org>
Commit-Queue: Florent Castelli <orphis@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#36423}
Diffstat (limited to 'media/sctp')
-rw-r--r-- | media/sctp/sctp_transport_factory.cc | 31 | ||||
-rw-r--r-- | media/sctp/sctp_transport_factory.h | 6 | ||||
-rw-r--r-- | media/sctp/usrsctp_transport.cc | 1575 | ||||
-rw-r--r-- | media/sctp/usrsctp_transport.h | 296 | ||||
-rw-r--r-- | media/sctp/usrsctp_transport_reliability_unittest.cc | 809 | ||||
-rw-r--r-- | media/sctp/usrsctp_transport_unittest.cc | 883 |
6 files changed, 7 insertions, 3593 deletions
diff --git a/media/sctp/sctp_transport_factory.cc b/media/sctp/sctp_transport_factory.cc index 51ce372d74..457bc5f889 100644 --- a/media/sctp/sctp_transport_factory.cc +++ b/media/sctp/sctp_transport_factory.cc @@ -10,29 +10,18 @@ #include "media/sctp/sctp_transport_factory.h" -#include "api/field_trials_view.h" #include "rtc_base/system/unused.h" #ifdef WEBRTC_HAVE_DCSCTP -#include "media/sctp/dcsctp_transport.h" // nogncheck -#include "system_wrappers/include/clock.h" // nogncheck -#endif - -#ifdef WEBRTC_HAVE_USRSCTP -#include "media/sctp/usrsctp_transport.h" // nogncheck +#include "media/sctp/dcsctp_transport.h" // nogncheck +#include "system_wrappers/include/clock.h" // nogncheck #endif namespace cricket { -SctpTransportFactory::SctpTransportFactory( - rtc::Thread* network_thread, - const webrtc::FieldTrialsView& field_trials) - : network_thread_(network_thread), use_usrsctp_("Disabled", false) { +SctpTransportFactory::SctpTransportFactory(rtc::Thread* network_thread) + : network_thread_(network_thread) { RTC_UNUSED(network_thread_); -#ifdef WEBRTC_HAVE_DCSCTP - webrtc::ParseFieldTrial({&use_usrsctp_}, - field_trials.Lookup("WebRTC-DataChannel-Dcsctp")); -#endif } std::unique_ptr<SctpTransportInternal> @@ -40,16 +29,8 @@ SctpTransportFactory::CreateSctpTransport( rtc::PacketTransportInternal* transport) { std::unique_ptr<SctpTransportInternal> result; #ifdef WEBRTC_HAVE_DCSCTP - if (!use_usrsctp_.Get()) { - result = std::unique_ptr<SctpTransportInternal>(new webrtc::DcSctpTransport( - network_thread_, transport, webrtc::Clock::GetRealTimeClock())); - } -#endif -#ifdef WEBRTC_HAVE_USRSCTP - if (!result) { - result = std::unique_ptr<SctpTransportInternal>( - new UsrsctpTransport(network_thread_, transport)); - } + result = std::unique_ptr<SctpTransportInternal>(new webrtc::DcSctpTransport( + network_thread_, transport, webrtc::Clock::GetRealTimeClock())); #endif return result; } diff --git a/media/sctp/sctp_transport_factory.h b/media/sctp/sctp_transport_factory.h index d117fdef5c..4fff214129 100644 --- a/media/sctp/sctp_transport_factory.h +++ b/media/sctp/sctp_transport_factory.h @@ -13,25 +13,21 @@ #include <memory> -#include "api/field_trials_view.h" #include "api/transport/sctp_transport_factory_interface.h" #include "media/sctp/sctp_transport_internal.h" -#include "rtc_base/experiments/field_trial_parser.h" #include "rtc_base/thread.h" namespace cricket { class SctpTransportFactory : public webrtc::SctpTransportFactoryInterface { public: - explicit SctpTransportFactory(rtc::Thread* network_thread, - const webrtc::FieldTrialsView& field_trials); + explicit SctpTransportFactory(rtc::Thread* network_thread); std::unique_ptr<SctpTransportInternal> CreateSctpTransport( rtc::PacketTransportInternal* transport) override; private: rtc::Thread* network_thread_; - webrtc::FieldTrialFlag use_usrsctp_; }; } // namespace cricket diff --git a/media/sctp/usrsctp_transport.cc b/media/sctp/usrsctp_transport.cc deleted file mode 100644 index 4babf110a2..0000000000 --- a/media/sctp/usrsctp_transport.cc +++ /dev/null @@ -1,1575 +0,0 @@ -/* - * Copyright (c) 2012 The WebRTC project authors. All Rights Reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#include <errno.h> -namespace { -// Some ERRNO values get re-#defined to WSA* equivalents in some talk/ -// headers. We save the original ones in an enum. -enum PreservedErrno { - SCTP_EINPROGRESS = EINPROGRESS, - SCTP_EWOULDBLOCK = EWOULDBLOCK -}; - -// Successful return value from usrsctp callbacks. Is not actually used by -// usrsctp, but all example programs for usrsctp use 1 as their return value. -constexpr int kSctpSuccessReturn = 1; -constexpr int kSctpErrorReturn = 0; - -} // namespace - -#include <stdarg.h> -#include <stdio.h> -#include <usrsctp.h> - -#include <memory> -#include <unordered_map> -#include <utility> - -#include "absl/algorithm/container.h" -#include "absl/base/attributes.h" -#include "absl/types/optional.h" -#include "api/sequence_checker.h" -#include "media/base/codec.h" -#include "media/base/media_channel.h" -#include "media/base/media_constants.h" -#include "media/base/stream_params.h" -#include "media/sctp/usrsctp_transport.h" -#include "p2p/base/dtls_transport_internal.h" // For PF_NORMAL -#include "rtc_base/arraysize.h" -#include "rtc_base/copy_on_write_buffer.h" -#include "rtc_base/helpers.h" -#include "rtc_base/logging.h" -#include "rtc_base/numerics/safe_conversions.h" -#include "rtc_base/string_utils.h" -#include "rtc_base/synchronization/mutex.h" -#include "rtc_base/task_utils/to_queued_task.h" -#include "rtc_base/thread_annotations.h" -#include "rtc_base/trace_event.h" - -namespace cricket { -namespace { - -// The biggest SCTP packet. Starting from a 'safe' wire MTU value of 1280, -// take off 85 bytes for DTLS/TURN/TCP/IP and ciphertext overhead. -// -// Additionally, it's possible that TURN adds an additional 4 bytes of overhead -// after a channel has been established, so we subtract an additional 4 bytes. -// -// 1280 IPV6 MTU -// -40 IPV6 header -// -8 UDP -// -24 GCM Cipher -// -13 DTLS record header -// -4 TURN ChannelData -// = 1191 bytes. -static constexpr size_t kSctpMtu = 1191; - -// Set the initial value of the static SCTP Data Engines reference count. -ABSL_CONST_INIT int g_usrsctp_usage_count = 0; -ABSL_CONST_INIT bool g_usrsctp_initialized_ = false; -ABSL_CONST_INIT webrtc::GlobalMutex g_usrsctp_lock_(absl::kConstInit); -ABSL_CONST_INIT char kZero[] = {'\0'}; - -// DataMessageType is used for the SCTP "Payload Protocol Identifier", as -// defined in http://tools.ietf.org/html/rfc4960#section-14.4 -// -// For the list of IANA approved values see: -// https://tools.ietf.org/html/rfc8831 Sec. 8 -// http://www.iana.org/assignments/sctp-parameters/sctp-parameters.xml -// The value is not used by SCTP itself. It indicates the protocol running -// on top of SCTP. -enum { - PPID_NONE = 0, // No protocol is specified. - PPID_CONTROL = 50, - PPID_TEXT_LAST = 51, - PPID_BINARY_PARTIAL = 52, // Deprecated - PPID_BINARY_LAST = 53, - PPID_TEXT_PARTIAL = 54, // Deprecated - PPID_TEXT_EMPTY = 56, - PPID_BINARY_EMPTY = 57, -}; - -// Should only be modified by UsrSctpWrapper. -ABSL_CONST_INIT cricket::UsrsctpTransportMap* g_transport_map_ = nullptr; - -// Helper that will call C's free automatically. -// TODO(b/181900299): Figure out why unique_ptr with a custom deleter is causing -// issues in a certain build environment. -class AutoFreedPointer { - public: - explicit AutoFreedPointer(void* ptr) : ptr_(ptr) {} - AutoFreedPointer(AutoFreedPointer&& o) : ptr_(o.ptr_) { o.ptr_ = nullptr; } - ~AutoFreedPointer() { free(ptr_); } - - void* get() const { return ptr_; } - - private: - void* ptr_; -}; - -// Helper for logging SCTP messages. -#if defined(__GNUC__) -__attribute__((__format__(__printf__, 1, 2))) -#endif -void DebugSctpPrintf(const char* format, ...) { -#if RTC_DCHECK_IS_ON - char s[255]; - va_list ap; - va_start(ap, format); - vsnprintf(s, sizeof(s), format, ap); - RTC_LOG(LS_INFO) << "SCTP: " << s; - va_end(ap); -#endif -} - -// Get the PPID to use for the terminating fragment of this type. -uint32_t GetPpid(webrtc::DataMessageType type, size_t size) { - switch (type) { - case webrtc::DataMessageType::kControl: - return PPID_CONTROL; - case webrtc::DataMessageType::kBinary: - return size > 0 ? PPID_BINARY_LAST : PPID_BINARY_EMPTY; - case webrtc::DataMessageType::kText: - return size > 0 ? PPID_TEXT_LAST : PPID_TEXT_EMPTY; - } -} - -bool GetDataMediaType(uint32_t ppid, webrtc::DataMessageType* dest) { - RTC_DCHECK(dest != NULL); - switch (ppid) { - case PPID_BINARY_PARTIAL: - case PPID_BINARY_LAST: - case PPID_BINARY_EMPTY: - *dest = webrtc::DataMessageType::kBinary; - return true; - - case PPID_TEXT_PARTIAL: - case PPID_TEXT_LAST: - case PPID_TEXT_EMPTY: - *dest = webrtc::DataMessageType::kText; - return true; - - case PPID_CONTROL: - *dest = webrtc::DataMessageType::kControl; - return true; - } - return false; -} - -bool IsEmptyPPID(uint32_t ppid) { - return ppid == PPID_BINARY_EMPTY || ppid == PPID_TEXT_EMPTY; -} - -// Log the packet in text2pcap format, if log level is at LS_VERBOSE. -// -// In order to turn these logs into a pcap file you can use, first filter the -// "SCTP_PACKET" log lines: -// -// cat chrome_debug.log | grep SCTP_PACKET > filtered.log -// -// Then run through text2pcap: -// -// text2pcap -n -l 248 -D -t '%H:%M:%S.' filtered.log filtered.pcapng -// -// Command flag information: -// -n: Outputs to a pcapng file, can specify inbound/outbound packets. -// -l: Specifies the link layer header type. 248 means SCTP. See: -// http://www.tcpdump.org/linktypes.html -// -D: Text before packet specifies if it is inbound or outbound. -// -t: Time format. -// -// Why do all this? Because SCTP goes over DTLS, which is encrypted. So just -// getting a normal packet capture won't help you, unless you have the DTLS -// keying material. -void VerboseLogPacket(const void* data, size_t length, int direction) { - if (RTC_LOG_CHECK_LEVEL(LS_VERBOSE) && length > 0) { - char* dump_buf; - // Some downstream project uses an older version of usrsctp that expects - // a non-const "void*" as first parameter when dumping the packet, so we - // need to cast the const away here to avoid a compiler error. - if ((dump_buf = usrsctp_dumppacket(const_cast<void*>(data), length, - direction)) != NULL) { - RTC_LOG(LS_VERBOSE) << dump_buf; - usrsctp_freedumpbuffer(dump_buf); - } - } -} - -// Creates the sctp_sendv_spa struct used for setting flags in the -// sctp_sendv() call. -sctp_sendv_spa CreateSctpSendParams(int sid, - const webrtc::SendDataParams& params, - size_t size) { - struct sctp_sendv_spa spa = {0}; - spa.sendv_flags |= SCTP_SEND_SNDINFO_VALID; - spa.sendv_sndinfo.snd_sid = sid; - spa.sendv_sndinfo.snd_ppid = rtc::HostToNetwork32(GetPpid(params.type, size)); - // Explicitly marking the EOR flag turns the usrsctp_sendv call below into a - // non atomic operation. This means that the sctp lib might only accept the - // message partially. This is done in order to improve throughput, so that we - // don't have to wait for an empty buffer to send the max message length, for - // example. - spa.sendv_sndinfo.snd_flags |= SCTP_EOR; - - if (!params.ordered) { - spa.sendv_sndinfo.snd_flags |= SCTP_UNORDERED; - } - if (params.max_rtx_count.has_value()) { - RTC_DCHECK(*params.max_rtx_count >= 0 && - *params.max_rtx_count <= std::numeric_limits<uint16_t>::max()); - spa.sendv_flags |= SCTP_SEND_PRINFO_VALID; - spa.sendv_prinfo.pr_policy = SCTP_PR_SCTP_RTX; - spa.sendv_prinfo.pr_value = *params.max_rtx_count; - } - if (params.max_rtx_ms.has_value()) { - RTC_DCHECK(*params.max_rtx_ms >= 0 && - *params.max_rtx_ms <= std::numeric_limits<uint16_t>::max()); - spa.sendv_flags |= SCTP_SEND_PRINFO_VALID; - spa.sendv_prinfo.pr_policy = SCTP_PR_SCTP_TTL; - spa.sendv_prinfo.pr_value = *params.max_rtx_ms; - } - return spa; -} - -std::string SctpErrorCauseCodeToString(SctpErrorCauseCode code) { - switch (code) { - case SctpErrorCauseCode::kInvalidStreamIdentifier: - return "Invalid Stream Identifier"; - case SctpErrorCauseCode::kMissingMandatoryParameter: - return "Missing Mandatory Parameter"; - case SctpErrorCauseCode::kStaleCookieError: - return "Stale Cookie Error"; - case SctpErrorCauseCode::kOutOfResource: - return "Out of Resource"; - case SctpErrorCauseCode::kUnresolvableAddress: - return "Unresolvable Address"; - case SctpErrorCauseCode::kUnrecognizedChunkType: - return "Unrecognized Chunk Type"; - case SctpErrorCauseCode::kInvalidMandatoryParameter: - return "Invalid Mandatory Parameter"; - case SctpErrorCauseCode::kUnrecognizedParameters: - return "Unrecognized Parameters"; - case SctpErrorCauseCode::kNoUserData: - return "No User Data"; - case SctpErrorCauseCode::kCookieReceivedWhileShuttingDown: - return "Cookie Received Whilte Shutting Down"; - case SctpErrorCauseCode::kRestartWithNewAddresses: - return "Restart With New Addresses"; - case SctpErrorCauseCode::kUserInitiatedAbort: - return "User Initiated Abort"; - case SctpErrorCauseCode::kProtocolViolation: - return "Protocol Violation"; - } - return "Unknown error"; -} -} // namespace - -// Maps SCTP transport ID to UsrsctpTransport object, necessary in send -// threshold callback and outgoing packet callback. It also provides a facility -// to safely post a task to an UsrsctpTransport's network thread from another -// thread. -class UsrsctpTransportMap { - public: - UsrsctpTransportMap() = default; - - // Assigns a new unused ID to the following transport. - uintptr_t Register(cricket::UsrsctpTransport* transport) { - webrtc::MutexLock lock(&lock_); - // usrsctp_connect fails with a value of 0... - if (next_id_ == 0) { - ++next_id_; - } - // In case we've wrapped around and need to find an empty spot from a - // removed transport. Assumes we'll never be full. - while (map_.find(next_id_) != map_.end()) { - ++next_id_; - if (next_id_ == 0) { - ++next_id_; - } - } - map_[next_id_] = transport; - return next_id_++; - } - - // Returns true if found. - bool Deregister(uintptr_t id) { - webrtc::MutexLock lock(&lock_); - return map_.erase(id) > 0; - } - - // Posts `action` to the network thread of the transport identified by `id` - // and returns true if found, all while holding a lock to protect against the - // transport being simultaneously deleted/deregistered, or returns false if - // not found. - template <typename F> - bool PostToTransportThread(uintptr_t id, F action) const { - webrtc::MutexLock lock(&lock_); - UsrsctpTransport* transport = RetrieveWhileHoldingLock(id); - if (!transport) { - return false; - } - transport->network_thread_->PostTask(ToQueuedTask( - transport->task_safety_, - [transport, action{std::move(action)}]() { action(transport); })); - return true; - } - - private: - UsrsctpTransport* RetrieveWhileHoldingLock(uintptr_t id) const - RTC_EXCLUSIVE_LOCKS_REQUIRED(lock_) { - auto it = map_.find(id); - if (it == map_.end()) { - return nullptr; - } - return it->second; - } - - mutable webrtc::Mutex lock_; - - uintptr_t next_id_ RTC_GUARDED_BY(lock_) = 0; - std::unordered_map<uintptr_t, UsrsctpTransport*> map_ RTC_GUARDED_BY(lock_); -}; - -// Handles global init/deinit, and mapping from usrsctp callbacks to -// UsrsctpTransport calls. -class UsrsctpTransport::UsrSctpWrapper { - public: - static void InitializeUsrSctp() { - RTC_LOG(LS_INFO) << __FUNCTION__; - // UninitializeUsrSctp tries to call usrsctp_finish in a loop for three - // seconds; if that failed and we were left in a still-initialized state, we - // don't want to call usrsctp_init again as that will result in undefined - // behavior. - if (g_usrsctp_initialized_) { - RTC_LOG(LS_WARNING) << "Not reinitializing usrsctp since last attempt at " - "usrsctp_finish failed."; - } else { - // First argument is udp_encapsulation_port, which is not releveant for - // our AF_CONN use of sctp. - usrsctp_init(0, &UsrSctpWrapper::OnSctpOutboundPacket, &DebugSctpPrintf); - g_usrsctp_initialized_ = true; - } - - // To turn on/off detailed SCTP debugging. You will also need to have the - // SCTP_DEBUG cpp defines flag, which can be turned on in media/BUILD.gn. - // usrsctp_sysctl_set_sctp_debug_on(SCTP_DEBUG_ALL); - - // TODO(ldixon): Consider turning this on/off. - usrsctp_sysctl_set_sctp_ecn_enable(0); - - // WebRTC doesn't use these features, so disable them to reduce the - // potential attack surface. - usrsctp_sysctl_set_sctp_asconf_enable(0); - usrsctp_sysctl_set_sctp_auth_enable(0); - - // This is harmless, but we should find out when the library default - // changes. - int send_size = usrsctp_sysctl_get_sctp_sendspace(); - if (send_size != kSctpSendBufferSize) { - RTC_LOG(LS_ERROR) << "Got different send size than expected: " - << send_size; - } - - // TODO(ldixon): Consider turning this on/off. - // This is not needed right now (we don't do dynamic address changes): - // If SCTP Auto-ASCONF is enabled, the peer is informed automatically - // when a new address is added or removed. This feature is enabled by - // default. - // usrsctp_sysctl_set_sctp_auto_asconf(0); - - // TODO(ldixon): Consider turning this on/off. - // Add a blackhole sysctl. Setting it to 1 results in no ABORTs - // being sent in response to INITs, setting it to 2 results - // in no ABORTs being sent for received OOTB packets. - // This is similar to the TCP sysctl. - // - // See: http://lakerest.net/pipermail/sctp-coders/2012-January/009438.html - // See: http://svnweb.freebsd.org/base?view=revision&revision=229805 - // usrsctp_sysctl_set_sctp_blackhole(2); - - // Set the number of default outgoing streams. This is the number we'll - // send in the SCTP INIT message. - usrsctp_sysctl_set_sctp_nr_outgoing_streams_default(kMaxSctpStreams); - - g_transport_map_ = new UsrsctpTransportMap(); - } - - static void UninitializeUsrSctp() { - RTC_LOG(LS_INFO) << __FUNCTION__; - // usrsctp_finish() may fail if it's called too soon after the transports - // are - // closed. Wait and try again until it succeeds for up to 3 seconds. - for (size_t i = 0; i < 300; ++i) { - if (usrsctp_finish() == 0) { - g_usrsctp_initialized_ = false; - delete g_transport_map_; - g_transport_map_ = nullptr; - return; - } - - rtc::Thread::SleepMs(10); - } - delete g_transport_map_; - g_transport_map_ = nullptr; - RTC_LOG(LS_ERROR) << "Failed to shutdown usrsctp."; - } - - static void IncrementUsrSctpUsageCount() { - webrtc::GlobalMutexLock lock(&g_usrsctp_lock_); - if (!g_usrsctp_usage_count) { - InitializeUsrSctp(); - } - ++g_usrsctp_usage_count; - } - - static void DecrementUsrSctpUsageCount() { - webrtc::GlobalMutexLock lock(&g_usrsctp_lock_); - --g_usrsctp_usage_count; - if (!g_usrsctp_usage_count) { - UninitializeUsrSctp(); - } - } - - // This is the callback usrsctp uses when there's data to send on the network - // that has been wrapped appropriatly for the SCTP protocol. - static int OnSctpOutboundPacket(void* addr, - void* data, - size_t length, - uint8_t tos, - uint8_t set_df) { - if (!g_transport_map_) { - RTC_LOG(LS_ERROR) - << "OnSctpOutboundPacket called after usrsctp uninitialized?"; - return EINVAL; - } - RTC_LOG(LS_VERBOSE) << "global OnSctpOutboundPacket():" - "addr: " - << addr << "; length: " << length - << "; tos: " << rtc::ToHex(tos) - << "; set_df: " << rtc::ToHex(set_df); - - VerboseLogPacket(data, length, SCTP_DUMP_OUTBOUND); - - // Note: We have to copy the data; the caller will delete it. - rtc::CopyOnWriteBuffer buf(reinterpret_cast<uint8_t*>(data), length); - - // PostsToTransportThread protects against the transport being - // simultaneously deregistered/deleted, since this callback may come from - // the SCTP timer thread and thus race with the network thread. - bool found = g_transport_map_->PostToTransportThread( - reinterpret_cast<uintptr_t>(addr), [buf](UsrsctpTransport* transport) { - transport->OnPacketFromSctpToNetwork(buf); - }); - if (!found) { - RTC_LOG(LS_ERROR) - << "OnSctpOutboundPacket: Failed to get transport for socket ID " - << addr << "; possibly was already destroyed."; - return EINVAL; - } - - return 0; - } - - // This is the callback called from usrsctp when data has been received, after - // a packet has been interpreted and parsed by usrsctp and found to contain - // payload data. It is called by a usrsctp thread. It is assumed this function - // will free the memory used by 'data'. - static int OnSctpInboundPacket(struct socket* sock, - union sctp_sockstore addr, - void* data, - size_t length, - struct sctp_rcvinfo rcv, - int flags, - void* ulp_info) { - AutoFreedPointer owned_data(data); - - if (!g_transport_map_) { - RTC_LOG(LS_ERROR) - << "OnSctpInboundPacket called after usrsctp uninitialized?"; - return kSctpErrorReturn; - } - - uintptr_t id = reinterpret_cast<uintptr_t>(ulp_info); - - // PostsToTransportThread protects against the transport being - // simultaneously deregistered/deleted, since this callback may come from - // the SCTP timer thread and thus race with the network thread. - bool found = g_transport_map_->PostToTransportThread( - id, [owned_data{std::move(owned_data)}, length, rcv, - flags](UsrsctpTransport* transport) { - transport->OnDataOrNotificationFromSctp(owned_data.get(), length, rcv, - flags); - }); - if (!found) { - RTC_LOG(LS_ERROR) - << "OnSctpInboundPacket: Failed to get transport for socket ID " << id - << "; possibly was already destroyed."; - return kSctpErrorReturn; - } - return kSctpSuccessReturn; - } - - static int SendThresholdCallback(struct socket* sock, - uint32_t sb_free, - void* ulp_info) { - // Fired on our I/O thread. UsrsctpTransport::OnPacketReceived() gets - // a packet containing acknowledgments, which goes into usrsctp_conninput, - // and then back here. - if (!g_transport_map_) { - RTC_LOG(LS_ERROR) - << "SendThresholdCallback called after usrsctp uninitialized?"; - return 0; - } - - uintptr_t id = reinterpret_cast<uintptr_t>(ulp_info); - - bool found = g_transport_map_->PostToTransportThread( - id, [](UsrsctpTransport* transport) { - transport->OnSendThresholdCallback(); - }); - if (!found) { - RTC_LOG(LS_ERROR) - << "SendThresholdCallback: Failed to get transport for socket ID " - << id << "; possibly was already destroyed."; - } - return 0; - } -}; - -UsrsctpTransport::UsrsctpTransport(rtc::Thread* network_thread, - rtc::PacketTransportInternal* transport) - : network_thread_(network_thread), - transport_(transport), - was_ever_writable_(transport ? transport->writable() : false) { - RTC_DCHECK(network_thread_); - RTC_DCHECK_RUN_ON(network_thread_); - ConnectTransportSignals(); -} - -UsrsctpTransport::~UsrsctpTransport() { - RTC_DCHECK_RUN_ON(network_thread_); - // Close abruptly; no reset procedure. - CloseSctpSocket(); - // It's not strictly necessary to reset these fields to nullptr, - // but having these fields set to nullptr is a clear indication that - // object was destructed. There was a bug in usrsctp when it - // invoked OnSctpOutboundPacket callback for destructed UsrsctpTransport, - // which caused obscure SIGSEGV on access to these fields, - // having this fields set to nullptr will make it easier to understand - // that UsrsctpTransport was destructed and "use-after-free" bug happen. - // SIGSEGV error triggered on dereference these pointers will also - // be easier to understand due to 0x0 address. All of this assumes - // that ASAN is not enabled to detect "use-after-free", which is - // currently default configuration. - network_thread_ = nullptr; - transport_ = nullptr; -} - -void UsrsctpTransport::SetDtlsTransport( - rtc::PacketTransportInternal* transport) { - RTC_DCHECK_RUN_ON(network_thread_); - DisconnectTransportSignals(); - transport_ = transport; - ConnectTransportSignals(); - if (!was_ever_writable_ && transport && transport->writable()) { - was_ever_writable_ = true; - // New transport is writable, now we can start the SCTP connection if Start - // was called already. - if (started_) { - RTC_DCHECK(!sock_); - Connect(); - } - } -} - -bool UsrsctpTransport::Start(int local_sctp_port, - int remote_sctp_port, - int max_message_size) { - RTC_DCHECK_RUN_ON(network_thread_); - if (local_sctp_port == -1) { - local_sctp_port = kSctpDefaultPort; - } - if (remote_sctp_port == -1) { - remote_sctp_port = kSctpDefaultPort; - } - if (max_message_size > kSctpSendBufferSize) { - RTC_LOG(LS_ERROR) << "Max message size of " << max_message_size - << " is larger than send bufffer size " - << kSctpSendBufferSize; - return false; - } - if (max_message_size < 1) { - RTC_LOG(LS_ERROR) << "Max message size of " << max_message_size - << " is too small"; - return false; - } - // We allow changing max_message_size with a second Start() call, - // but not changing the port numbers. - max_message_size_ = max_message_size; - if (started_) { - if (local_sctp_port != local_port_ || remote_sctp_port != remote_port_) { - RTC_LOG(LS_ERROR) - << "Can't change SCTP port after SCTP association formed."; - return false; - } - return true; - } - local_port_ = local_sctp_port; - remote_port_ = remote_sctp_port; - started_ = true; - RTC_DCHECK(!sock_); - // Only try to connect if the DTLS transport has been writable before - // (indicating that the DTLS handshake is complete). - if (was_ever_writable_) { - return Connect(); - } - return true; -} - -bool UsrsctpTransport::OpenStream(int sid) { - RTC_DCHECK_RUN_ON(network_thread_); - if (sid > kMaxSctpSid) { - RTC_LOG(LS_WARNING) << debug_name_ - << "->OpenStream(...): " - "Not adding data stream " - "with sid=" - << sid << " because sid is too high."; - return false; - } - auto it = stream_status_by_sid_.find(sid); - if (it == stream_status_by_sid_.end()) { - stream_status_by_sid_[sid] = StreamStatus(); - return true; - } - if (it->second.is_open()) { - RTC_LOG(LS_WARNING) << debug_name_ - << "->OpenStream(...): " - "Not adding data stream " - "with sid=" - << sid << " because stream is already open."; - return false; - } else { - RTC_LOG(LS_WARNING) << debug_name_ - << "->OpenStream(...): " - "Not adding data stream " - " with sid=" - << sid << " because stream is still closing."; - return false; - } -} - -bool UsrsctpTransport::ResetStream(int sid) { - RTC_DCHECK_RUN_ON(network_thread_); - - auto it = stream_status_by_sid_.find(sid); - if (it == stream_status_by_sid_.end() || !it->second.is_open()) { - RTC_LOG(LS_WARNING) << debug_name_ << "->ResetStream(" << sid - << "): stream not open."; - return false; - } - - RTC_LOG(LS_VERBOSE) << debug_name_ << "->ResetStream(" << sid - << "): " - "Queuing RE-CONFIG chunk."; - it->second.closure_initiated = true; - - // Signal our stream-reset logic that it should try to send now, if it can. - SendQueuedStreamResets(); - - // The stream will actually get removed when we get the acknowledgment. - return true; -} - -bool UsrsctpTransport::SendData(int sid, - const webrtc::SendDataParams& params, - const rtc::CopyOnWriteBuffer& payload, - SendDataResult* result) { - RTC_DCHECK_RUN_ON(network_thread_); - - if (partial_outgoing_message_.has_value()) { - if (result) { - *result = SDR_BLOCK; - } - // Ready to send should get set only when SendData() call gets blocked. - ready_to_send_data_ = false; - return false; - } - - // Do not queue data to send on a closing stream. - auto it = stream_status_by_sid_.find(sid); - if (it == stream_status_by_sid_.end() || !it->second.is_open()) { - RTC_LOG(LS_WARNING) - << debug_name_ - << "->SendData(...): " - "Not sending data because sid is unknown or closing: " - << sid; - if (result) { - *result = SDR_ERROR; - } - return false; - } - - size_t payload_size = payload.size(); - OutgoingMessage message(payload, sid, params); - SendDataResult send_message_result = SendMessageInternal(&message); - if (result) { - *result = send_message_result; - } - if (payload_size == message.size()) { - // Nothing was sent. - return false; - } - // If any data is sent, we accept the message. In the case that data was - // partially accepted by the sctp library, the remaining is buffered. This - // ensures the client does not resend the message. - RTC_DCHECK_LT(message.size(), payload_size); - if (message.size() > 0) { - RTC_DCHECK(!partial_outgoing_message_.has_value()); - RTC_DLOG(LS_VERBOSE) << "Partially sent message. Buffering the remaining" - << message.size() << "/" << payload_size << " bytes."; - - partial_outgoing_message_.emplace(message); - } - return true; -} - -SendDataResult UsrsctpTransport::SendMessageInternal(OutgoingMessage* message) { - RTC_DCHECK_RUN_ON(network_thread_); - if (!sock_) { - RTC_LOG(LS_WARNING) << debug_name_ - << "->SendMessageInternal(...): " - "Not sending packet with sid=" - << message->sid() << " len=" << message->size() - << " before Start()."; - return SDR_ERROR; - } - if (message->send_params().type != webrtc::DataMessageType::kControl) { - auto it = stream_status_by_sid_.find(message->sid()); - if (it == stream_status_by_sid_.end()) { - RTC_LOG(LS_WARNING) << debug_name_ - << "->SendMessageInternal(...): " - "Not sending data because sid is unknown: " - << message->sid(); - return SDR_ERROR; - } - } - if (message->size() > static_cast<size_t>(max_message_size_)) { - RTC_LOG(LS_ERROR) << "Attempting to send message of size " - << message->size() << " which is larger than limit " - << max_message_size_; - return SDR_ERROR; - } - - // Send data using SCTP. - sctp_sendv_spa spa = CreateSctpSendParams( - message->sid(), message->send_params(), message->size()); - const void* data = message->data(); - size_t data_length = message->size(); - if (message->size() == 0) { - // Empty messages are replaced by a single NUL byte on the wire as SCTP - // doesn't support empty messages. - // The PPID carries the information that the payload needs to be ignored. - data = kZero; - data_length = 1; - } - // Note: this send call is not atomic because the EOR bit is set. This means - // that usrsctp can partially accept this message and it is our duty to buffer - // the rest. - ssize_t send_res = usrsctp_sendv(sock_, data, data_length, NULL, 0, &spa, - rtc::checked_cast<socklen_t>(sizeof(spa)), - SCTP_SENDV_SPA, 0); - if (send_res < 0) { - if (errno == SCTP_EWOULDBLOCK) { - ready_to_send_data_ = false; - RTC_LOG(LS_VERBOSE) << debug_name_ - << "->SendMessageInternal(...): EWOULDBLOCK returned"; - return SDR_BLOCK; - } - - RTC_LOG_ERRNO(LS_ERROR) << "ERROR:" << debug_name_ - << "->SendMessageInternal(...): " - " usrsctp_sendv: "; - return SDR_ERROR; - } - - size_t amount_sent = static_cast<size_t>(send_res); - RTC_DCHECK_LE(amount_sent, data_length); - if (message->size() != 0) - message->Advance(amount_sent); - // Only way out now is success. - return SDR_SUCCESS; -} - -bool UsrsctpTransport::ReadyToSendData() { - RTC_DCHECK_RUN_ON(network_thread_); - return ready_to_send_data_; -} - -void UsrsctpTransport::ConnectTransportSignals() { - RTC_DCHECK_RUN_ON(network_thread_); - if (!transport_) { - return; - } - transport_->SignalWritableState.connect(this, - &UsrsctpTransport::OnWritableState); - transport_->SignalReadPacket.connect(this, &UsrsctpTransport::OnPacketRead); - transport_->SignalClosed.connect(this, &UsrsctpTransport::OnClosed); -} - -void UsrsctpTransport::DisconnectTransportSignals() { - RTC_DCHECK_RUN_ON(network_thread_); - if (!transport_) { - return; - } - transport_->SignalWritableState.disconnect(this); - transport_->SignalReadPacket.disconnect(this); - transport_->SignalClosed.disconnect(this); -} - -bool UsrsctpTransport::Connect() { - RTC_DCHECK_RUN_ON(network_thread_); - RTC_LOG(LS_VERBOSE) << debug_name_ << "->Connect()."; - - // If we already have a socket connection (which shouldn't ever happen), just - // return. - RTC_DCHECK(!sock_); - if (sock_) { - RTC_LOG(LS_ERROR) << debug_name_ - << "->Connect(): Ignored as socket " - "is already established."; - return true; - } - - // If no socket (it was closed) try to start it again. This can happen when - // the socket we are connecting to closes, does an sctp shutdown handshake, - // or behaves unexpectedly causing us to perform a CloseSctpSocket. - if (!OpenSctpSocket()) { - return false; - } - - // Note: conversion from int to uint16_t happens on assignment. - sockaddr_conn local_sconn = GetSctpSockAddr(local_port_); - if (usrsctp_bind(sock_, reinterpret_cast<sockaddr*>(&local_sconn), - sizeof(local_sconn)) < 0) { - RTC_LOG_ERRNO(LS_ERROR) - << debug_name_ << "->Connect(): " << ("Failed usrsctp_bind"); - CloseSctpSocket(); - return false; - } - - // Note: conversion from int to uint16_t happens on assignment. - sockaddr_conn remote_sconn = GetSctpSockAddr(remote_port_); - int connect_result = usrsctp_connect( - sock_, reinterpret_cast<sockaddr*>(&remote_sconn), sizeof(remote_sconn)); - if (connect_result < 0 && errno != SCTP_EINPROGRESS) { - RTC_LOG_ERRNO(LS_ERROR) << debug_name_ - << "->Connect(): " - "Failed usrsctp_connect. got errno=" - << errno << ", but wanted " << SCTP_EINPROGRESS; - CloseSctpSocket(); - return false; - } - // Set the MTU and disable MTU discovery. - // We can only do this after usrsctp_connect or it has no effect. - sctp_paddrparams params = {}; - memcpy(¶ms.spp_address, &remote_sconn, sizeof(remote_sconn)); - params.spp_flags = SPP_PMTUD_DISABLE; - // The MTU value provided specifies the space available for chunks in the - // packet, so we subtract the SCTP header size. - params.spp_pathmtu = kSctpMtu - sizeof(struct sctp_common_header); - if (usrsctp_setsockopt(sock_, IPPROTO_SCTP, SCTP_PEER_ADDR_PARAMS, ¶ms, - sizeof(params))) { - RTC_LOG_ERRNO(LS_ERROR) << debug_name_ - << "->Connect(): " - "Failed to set SCTP_PEER_ADDR_PARAMS."; - } - // Since this is a fresh SCTP association, we'll always start out with empty - // queues, so "ReadyToSendData" should be true. - SetReadyToSendData(); - return true; -} - -bool UsrsctpTransport::OpenSctpSocket() { - RTC_DCHECK_RUN_ON(network_thread_); - if (sock_) { - RTC_LOG(LS_WARNING) << debug_name_ - << "->OpenSctpSocket(): " - "Ignoring attempt to re-create existing socket."; - return false; - } - - UsrSctpWrapper::IncrementUsrSctpUsageCount(); - - // If kSctpSendBufferSize isn't reflective of reality, we log an error, but we - // still have to do something reasonable here. Look up what the buffer's real - // size is and set our threshold to something reasonable. - // TODO(bugs.webrtc.org/11824): That was previously set to 50%, not 25%, but - // it was reduced to a recent usrsctp regression. Can return to 50% when the - // root cause is fixed. - static const int kSendThreshold = usrsctp_sysctl_get_sctp_sendspace() / 4; - - sock_ = usrsctp_socket( - AF_CONN, SOCK_STREAM, IPPROTO_SCTP, &UsrSctpWrapper::OnSctpInboundPacket, - &UsrSctpWrapper::SendThresholdCallback, kSendThreshold, nullptr); - if (!sock_) { - RTC_LOG_ERRNO(LS_ERROR) << debug_name_ - << "->OpenSctpSocket(): " - "Failed to create SCTP socket."; - UsrSctpWrapper::DecrementUsrSctpUsageCount(); - return false; - } - - if (!ConfigureSctpSocket()) { - usrsctp_close(sock_); - sock_ = nullptr; - UsrSctpWrapper::DecrementUsrSctpUsageCount(); - return false; - } - id_ = g_transport_map_->Register(this); - usrsctp_set_ulpinfo(sock_, reinterpret_cast<void*>(id_)); - // Register our id as an address for usrsctp. This is used by SCTP to - // direct the packets received (by the created socket) to this class. - usrsctp_register_address(reinterpret_cast<void*>(id_)); - return true; -} - -bool UsrsctpTransport::ConfigureSctpSocket() { - RTC_DCHECK_RUN_ON(network_thread_); - RTC_DCHECK(sock_); - // Make the socket non-blocking. Connect, close, shutdown etc will not block - // the thread waiting for the socket operation to complete. - if (usrsctp_set_non_blocking(sock_, 1) < 0) { - RTC_LOG_ERRNO(LS_ERROR) << debug_name_ - << "->ConfigureSctpSocket(): " - "Failed to set SCTP to non blocking."; - return false; - } - - // This ensures that the usrsctp close call deletes the association. This - // prevents usrsctp from calling OnSctpOutboundPacket with references to - // this class as the address. - linger linger_opt; - linger_opt.l_onoff = 1; - linger_opt.l_linger = 0; - if (usrsctp_setsockopt(sock_, SOL_SOCKET, SO_LINGER, &linger_opt, - sizeof(linger_opt))) { - RTC_LOG_ERRNO(LS_ERROR) << debug_name_ - << "->ConfigureSctpSocket(): " - "Failed to set SO_LINGER."; - return false; - } - - // Enable stream ID resets. - struct sctp_assoc_value stream_rst; - stream_rst.assoc_id = SCTP_ALL_ASSOC; - stream_rst.assoc_value = 1; - if (usrsctp_setsockopt(sock_, IPPROTO_SCTP, SCTP_ENABLE_STREAM_RESET, - &stream_rst, sizeof(stream_rst))) { - RTC_LOG_ERRNO(LS_ERROR) << debug_name_ - << "->ConfigureSctpSocket(): " - "Failed to set SCTP_ENABLE_STREAM_RESET."; - return false; - } - - // Nagle. - uint32_t nodelay = 1; - if (usrsctp_setsockopt(sock_, IPPROTO_SCTP, SCTP_NODELAY, &nodelay, - sizeof(nodelay))) { - RTC_LOG_ERRNO(LS_ERROR) << debug_name_ - << "->ConfigureSctpSocket(): " - "Failed to set SCTP_NODELAY."; - return false; - } - - // Explicit EOR. - uint32_t eor = 1; - if (usrsctp_setsockopt(sock_, IPPROTO_SCTP, SCTP_EXPLICIT_EOR, &eor, - sizeof(eor))) { - RTC_LOG_ERRNO(LS_ERROR) << debug_name_ - << "->ConfigureSctpSocket(): " - "Failed to set SCTP_EXPLICIT_EOR."; - return false; - } - - // Subscribe to SCTP event notifications. - // TODO(crbug.com/1137936): Subscribe to SCTP_SEND_FAILED_EVENT once deadlock - // is fixed upstream, or we switch to the upcall API: - // https://github.com/sctplab/usrsctp/issues/537 - int event_types[] = {SCTP_ASSOC_CHANGE, SCTP_PEER_ADDR_CHANGE, - SCTP_SENDER_DRY_EVENT, SCTP_STREAM_RESET_EVENT}; - struct sctp_event event = {0}; - event.se_assoc_id = SCTP_ALL_ASSOC; - event.se_on = 1; - for (size_t i = 0; i < arraysize(event_types); i++) { - event.se_type = event_types[i]; - if (usrsctp_setsockopt(sock_, IPPROTO_SCTP, SCTP_EVENT, &event, - sizeof(event)) < 0) { - RTC_LOG_ERRNO(LS_ERROR) << debug_name_ - << "->ConfigureSctpSocket(): " - "Failed to set SCTP_EVENT type: " - << event.se_type; - return false; - } - } - return true; -} - -void UsrsctpTransport::CloseSctpSocket() { - RTC_DCHECK_RUN_ON(network_thread_); - if (sock_) { - // We assume that SO_LINGER option is set to close the association when - // close is called. This means that any pending packets in usrsctp will be - // discarded instead of being sent. - usrsctp_close(sock_); - sock_ = nullptr; - usrsctp_deregister_address(reinterpret_cast<void*>(id_)); - RTC_CHECK(g_transport_map_->Deregister(id_)); - UsrSctpWrapper::DecrementUsrSctpUsageCount(); - ready_to_send_data_ = false; - } -} - -bool UsrsctpTransport::SendQueuedStreamResets() { - RTC_DCHECK_RUN_ON(network_thread_); - - auto needs_reset = - [this](const std::map<uint32_t, StreamStatus>::value_type& stream) { - // Ignore streams with partial outgoing messages as they are required to - // be fully sent by the WebRTC spec - // https://w3c.github.io/webrtc-pc/#closing-procedure - return stream.second.need_outgoing_reset() && - (!partial_outgoing_message_.has_value() || - partial_outgoing_message_.value().sid() != - static_cast<int>(stream.first)); - }; - // Figure out how many streams need to be reset. We need to do this so we can - // allocate the right amount of memory for the sctp_reset_streams structure. - size_t num_streams = absl::c_count_if(stream_status_by_sid_, needs_reset); - if (num_streams == 0) { - // Nothing to reset. - return true; - } - - RTC_LOG(LS_VERBOSE) << "SendQueuedStreamResets[" << debug_name_ - << "]: Resetting " << num_streams << " outgoing streams."; - - const size_t num_bytes = - sizeof(struct sctp_reset_streams) + (num_streams * sizeof(uint16_t)); - std::vector<uint8_t> reset_stream_buf(num_bytes, 0); - struct sctp_reset_streams* resetp = - reinterpret_cast<sctp_reset_streams*>(&reset_stream_buf[0]); - resetp->srs_assoc_id = SCTP_ALL_ASSOC; - resetp->srs_flags = SCTP_STREAM_RESET_OUTGOING; - resetp->srs_number_streams = rtc::checked_cast<uint16_t>(num_streams); - int result_idx = 0; - - for (const auto& stream : stream_status_by_sid_) { - if (needs_reset(stream)) { - resetp->srs_stream_list[result_idx++] = stream.first; - } - } - - int ret = - usrsctp_setsockopt(sock_, IPPROTO_SCTP, SCTP_RESET_STREAMS, resetp, - rtc::checked_cast<socklen_t>(reset_stream_buf.size())); - if (ret < 0) { - // Note that usrsctp only lets us have one reset in progress at a time - // (even though multiple streams can be reset at once). If this happens, - // SendQueuedStreamResets will end up called after the current in-progress - // reset finishes, in OnStreamResetEvent. - RTC_LOG_ERRNO(LS_WARNING) << debug_name_ - << "->SendQueuedStreamResets(): " - "Failed to send a stream reset for " - << num_streams << " streams"; - return false; - } - - // Since the usrsctp call completed successfully, update our stream status - // map to note that we started the outgoing reset. - for (auto it = stream_status_by_sid_.begin(); - it != stream_status_by_sid_.end(); ++it) { - if (it->second.need_outgoing_reset()) { - it->second.outgoing_reset_initiated = true; - } - } - return true; -} - -void UsrsctpTransport::SetReadyToSendData() { - RTC_DCHECK_RUN_ON(network_thread_); - if (!ready_to_send_data_) { - ready_to_send_data_ = true; - SignalReadyToSendData(); - } -} - -bool UsrsctpTransport::SendBufferedMessage() { - RTC_DCHECK_RUN_ON(network_thread_); - RTC_DCHECK(partial_outgoing_message_.has_value()); - RTC_DLOG(LS_VERBOSE) << "Sending partially buffered message of size " - << partial_outgoing_message_->size() << "."; - - SendMessageInternal(&partial_outgoing_message_.value()); - if (partial_outgoing_message_->size() > 0) { - // Still need to finish sending the message. - return false; - } - RTC_DCHECK_EQ(0u, partial_outgoing_message_->size()); - - int sid = partial_outgoing_message_->sid(); - partial_outgoing_message_.reset(); - - // Send the queued stream reset if it was pending for this stream. - auto it = stream_status_by_sid_.find(sid); - if (it->second.need_outgoing_reset()) { - SendQueuedStreamResets(); - } - - return true; -} - -void UsrsctpTransport::OnWritableState( - rtc::PacketTransportInternal* transport) { - RTC_DCHECK_RUN_ON(network_thread_); - RTC_DCHECK_EQ(transport_, transport); - if (!was_ever_writable_ && transport->writable()) { - was_ever_writable_ = true; - if (started_) { - Connect(); - } - } -} - -// Called by network interface when a packet has been received. -void UsrsctpTransport::OnPacketRead(rtc::PacketTransportInternal* transport, - const char* data, - size_t len, - const int64_t& /* packet_time_us */, - int flags) { - RTC_DCHECK_RUN_ON(network_thread_); - RTC_DCHECK_EQ(transport_, transport); - TRACE_EVENT0("webrtc", "UsrsctpTransport::OnPacketRead"); - - if (flags & PF_SRTP_BYPASS) { - // We are only interested in SCTP packets. - return; - } - - RTC_LOG(LS_VERBOSE) << debug_name_ - << "->OnPacketRead(...): " - " length=" - << len << ", started: " << started_; - // Only give receiving packets to usrsctp after if connected. This enables two - // peers to each make a connect call, but for them not to receive an INIT - // packet before they have called connect; least the last receiver of the INIT - // packet will have called connect, and a connection will be established. - if (sock_) { - // Pass received packet to SCTP stack. Once processed by usrsctp, the data - // will be will be given to the global OnSctpInboundPacket callback and - // posted to the transport thread. - VerboseLogPacket(data, len, SCTP_DUMP_INBOUND); - usrsctp_conninput(reinterpret_cast<void*>(id_), data, len, 0); - } else { - // TODO(ldixon): Consider caching the packet for very slightly better - // reliability. - } -} - -void UsrsctpTransport::OnClosed(rtc::PacketTransportInternal* transport) { - webrtc::RTCError error = - webrtc::RTCError(webrtc::RTCErrorType::OPERATION_ERROR_WITH_DATA, - "Transport channel closed"); - error.set_error_detail(webrtc::RTCErrorDetailType::SCTP_FAILURE); - SignalClosedAbruptly(error); -} - -void UsrsctpTransport::OnSendThresholdCallback() { - RTC_DCHECK_RUN_ON(network_thread_); - if (partial_outgoing_message_.has_value()) { - if (!SendBufferedMessage()) { - // Did not finish sending the buffered message. - return; - } - } - SetReadyToSendData(); -} - -sockaddr_conn UsrsctpTransport::GetSctpSockAddr(int port) { - sockaddr_conn sconn = {0}; - sconn.sconn_family = AF_CONN; -#ifdef HAVE_SCONN_LEN - sconn.sconn_len = sizeof(sockaddr_conn); -#endif - // Note: conversion from int to uint16_t happens here. - sconn.sconn_port = rtc::HostToNetwork16(port); - sconn.sconn_addr = reinterpret_cast<void*>(id_); - return sconn; -} - -void UsrsctpTransport::OnPacketFromSctpToNetwork( - const rtc::CopyOnWriteBuffer& buffer) { - RTC_DCHECK_RUN_ON(network_thread_); - if (buffer.size() > (kSctpMtu)) { - RTC_LOG(LS_ERROR) << debug_name_ - << "->OnPacketFromSctpToNetwork(...): " - "SCTP seems to have made a packet that is bigger " - "than its official MTU: " - << buffer.size() << " vs max of " << kSctpMtu; - } - TRACE_EVENT0("webrtc", "UsrsctpTransport::OnPacketFromSctpToNetwork"); - - // Don't create noise by trying to send a packet when the DTLS transport isn't - // even writable. - if (!transport_ || !transport_->writable()) { - return; - } - - // Bon voyage. - transport_->SendPacket(buffer.data<char>(), buffer.size(), - rtc::PacketOptions(), PF_NORMAL); -} - -void UsrsctpTransport::InjectDataOrNotificationFromSctpForTesting( - const void* data, - size_t length, - struct sctp_rcvinfo rcv, - int flags) { - OnDataOrNotificationFromSctp(data, length, rcv, flags); -} - -void UsrsctpTransport::OnDataOrNotificationFromSctp(const void* data, - size_t length, - struct sctp_rcvinfo rcv, - int flags) { - RTC_DCHECK_RUN_ON(network_thread_); - // If data is NULL, the SCTP association has been closed. - if (!data) { - RTC_LOG(LS_INFO) << debug_name_ - << "->OnDataOrNotificationFromSctp(...): " - "No data; association closed."; - return; - } - - // Handle notifications early. - // Note: Notifications are never split into chunks, so they can and should - // be handled early and entirely separate from the reassembly - // process. - if (flags & MSG_NOTIFICATION) { - RTC_LOG(LS_VERBOSE) - << debug_name_ - << "->OnDataOrNotificationFromSctp(...): SCTP notification" - << " length=" << length; - - rtc::CopyOnWriteBuffer notification(reinterpret_cast<const uint8_t*>(data), - length); - OnNotificationFromSctp(notification); - return; - } - - // Log data chunk - const uint32_t ppid = rtc::NetworkToHost32(rcv.rcv_ppid); - RTC_LOG(LS_VERBOSE) << debug_name_ - << "->OnDataOrNotificationFromSctp(...): SCTP data chunk" - << " length=" << length << ", sid=" << rcv.rcv_sid - << ", ppid=" << ppid << ", ssn=" << rcv.rcv_ssn - << ", cum-tsn=" << rcv.rcv_cumtsn - << ", eor=" << ((flags & MSG_EOR) ? "y" : "n"); - - // Validate payload protocol identifier - webrtc::DataMessageType type; - if (!GetDataMediaType(ppid, &type)) { - // Unexpected PPID, dropping - RTC_LOG(LS_ERROR) << "Received an unknown PPID " << ppid - << " on an SCTP packet. Dropping."; - return; - } - - // Expect only continuation messages belonging to the same SID. The SCTP - // stack is expected to ensure this as long as the User Message - // Interleaving extension (RFC 8260) is not explicitly enabled, so this - // merely acts as a safeguard. - if ((partial_incoming_message_.size() != 0) && - (rcv.rcv_sid != partial_params_.sid)) { - RTC_LOG(LS_ERROR) << "Received a new SID without EOR in the previous" - << " SCTP packet. Discarding the previous packet."; - partial_incoming_message_.Clear(); - } - - // Copy metadata of interest - ReceiveDataParams params; - params.type = type; - params.sid = rcv.rcv_sid; - // Note that the SSN is identical for each chunk of the same message. - // Furthermore, it is increased per stream and not on the whole - // association. - params.seq_num = rcv.rcv_ssn; - - // Append the chunk's data to the message buffer unless we have a chunk with a - // PPID marking an empty message. - // See: https://tools.ietf.org/html/rfc8831#section-6.6 - if (!IsEmptyPPID(ppid)) - partial_incoming_message_.AppendData(reinterpret_cast<const uint8_t*>(data), - length); - partial_params_ = params; - partial_flags_ = flags; - - // If the message is not yet complete... - if (!(flags & MSG_EOR)) { - if (partial_incoming_message_.size() < kSctpSendBufferSize) { - // We still have space in the buffer. Continue buffering chunks until - // the message is complete before handing it out. - return; - } else { - // The sender is exceeding the maximum message size that we announced. - // Spit out a warning but still hand out the partial message. Note that - // this behaviour is undesirable, see the discussion in issue 7774. - // - // TODO(lgrahl): Once sufficient time has passed and all supported - // browser versions obey the announced maximum message size, we should - // abort the SCTP association instead to prevent message integrity - // violation. - RTC_LOG(LS_ERROR) << "Handing out partial SCTP message."; - } - } - - // Dispatch the complete message and reset the message buffer. - OnDataFromSctpToTransport(params, partial_incoming_message_); - partial_incoming_message_.Clear(); -} - -void UsrsctpTransport::OnDataFromSctpToTransport( - const ReceiveDataParams& params, - const rtc::CopyOnWriteBuffer& buffer) { - RTC_DCHECK_RUN_ON(network_thread_); - RTC_LOG(LS_VERBOSE) << debug_name_ - << "->OnDataFromSctpToTransport(...): " - "Posting with length: " - << buffer.size() << " on stream " << params.sid; - // Reports all received messages to upper layers, no matter whether the sid - // is known. - SignalDataReceived(params, buffer); -} - -void UsrsctpTransport::OnNotificationFromSctp( - const rtc::CopyOnWriteBuffer& buffer) { - RTC_DCHECK_RUN_ON(network_thread_); - if (buffer.size() < sizeof(sctp_notification::sn_header)) { - RTC_LOG(LS_ERROR) << "SCTP notification is shorter than header size: " - << buffer.size(); - return; - } - - const sctp_notification& notification = - reinterpret_cast<const sctp_notification&>(*buffer.data()); - if (buffer.size() != notification.sn_header.sn_length) { - RTC_LOG(LS_ERROR) << "SCTP notification length (" << buffer.size() - << ") does not match sn_length field (" - << notification.sn_header.sn_length << ")."; - return; - } - - // TODO(ldixon): handle notifications appropriately. - switch (notification.sn_header.sn_type) { - case SCTP_ASSOC_CHANGE: - RTC_LOG(LS_VERBOSE) << "SCTP_ASSOC_CHANGE"; - if (buffer.size() < sizeof(notification.sn_assoc_change)) { - RTC_LOG(LS_ERROR) - << "SCTP_ASSOC_CHANGE notification has less than required length: " - << buffer.size(); - return; - } - OnNotificationAssocChange(notification.sn_assoc_change); - break; - case SCTP_REMOTE_ERROR: - RTC_LOG(LS_INFO) << "SCTP_REMOTE_ERROR"; - break; - case SCTP_SHUTDOWN_EVENT: - RTC_LOG(LS_INFO) << "SCTP_SHUTDOWN_EVENT"; - break; - case SCTP_ADAPTATION_INDICATION: - RTC_LOG(LS_INFO) << "SCTP_ADAPTATION_INDICATION"; - break; - case SCTP_PARTIAL_DELIVERY_EVENT: - RTC_LOG(LS_INFO) << "SCTP_PARTIAL_DELIVERY_EVENT"; - break; - case SCTP_AUTHENTICATION_EVENT: - RTC_LOG(LS_INFO) << "SCTP_AUTHENTICATION_EVENT"; - break; - case SCTP_SENDER_DRY_EVENT: - RTC_LOG(LS_VERBOSE) << "SCTP_SENDER_DRY_EVENT"; - SetReadyToSendData(); - break; - // TODO(ldixon): Unblock after congestion. - case SCTP_NOTIFICATIONS_STOPPED_EVENT: - RTC_LOG(LS_INFO) << "SCTP_NOTIFICATIONS_STOPPED_EVENT"; - break; - case SCTP_SEND_FAILED_EVENT: { - if (buffer.size() < sizeof(notification.sn_send_failed_event)) { - RTC_LOG(LS_ERROR) << "SCTP_SEND_FAILED_EVENT notification has less " - "than required length: " - << buffer.size(); - return; - } - const struct sctp_send_failed_event& ssfe = - notification.sn_send_failed_event; - RTC_LOG(LS_WARNING) << "SCTP_SEND_FAILED_EVENT: message with" - " PPID = " - << rtc::NetworkToHost32(ssfe.ssfe_info.snd_ppid) - << " SID = " << ssfe.ssfe_info.snd_sid - << " flags = " << rtc::ToHex(ssfe.ssfe_info.snd_flags) - << " failed to sent due to error = " - << rtc::ToHex(ssfe.ssfe_error); - break; - } - case SCTP_STREAM_RESET_EVENT: - if (buffer.size() < sizeof(notification.sn_strreset_event)) { - RTC_LOG(LS_ERROR) << "SCTP_STREAM_RESET_EVENT notification has less " - "than required length: " - << buffer.size(); - return; - } - OnStreamResetEvent(¬ification.sn_strreset_event); - break; - case SCTP_ASSOC_RESET_EVENT: - RTC_LOG(LS_INFO) << "SCTP_ASSOC_RESET_EVENT"; - break; - case SCTP_STREAM_CHANGE_EVENT: - RTC_LOG(LS_INFO) << "SCTP_STREAM_CHANGE_EVENT"; - // An acknowledgment we get after our stream resets have gone through, - // if they've failed. We log the message, but don't react -- we don't - // keep around the last-transmitted set of SSIDs we wanted to close for - // error recovery. It doesn't seem likely to occur, and if so, likely - // harmless within the lifetime of a single SCTP association. - break; - case SCTP_PEER_ADDR_CHANGE: - RTC_LOG(LS_INFO) << "SCTP_PEER_ADDR_CHANGE"; - break; - default: - RTC_LOG(LS_WARNING) << "Unknown SCTP event: " - << notification.sn_header.sn_type; - break; - } -} - -void UsrsctpTransport::OnNotificationAssocChange( - const sctp_assoc_change& change) { - RTC_DCHECK_RUN_ON(network_thread_); - switch (change.sac_state) { - case SCTP_COMM_UP: - RTC_LOG(LS_VERBOSE) << "Association change SCTP_COMM_UP, stream # is " - << change.sac_outbound_streams << " outbound, " - << change.sac_inbound_streams << " inbound."; - max_outbound_streams_ = change.sac_outbound_streams; - max_inbound_streams_ = change.sac_inbound_streams; - SignalAssociationChangeCommunicationUp(); - // In case someone tried to close a stream before communication - // came up, send any queued resets. - SendQueuedStreamResets(); - break; - case SCTP_COMM_LOST: { - RTC_LOG(LS_INFO) << "Association change SCTP_COMM_LOST"; - webrtc::RTCError error = webrtc::RTCError( - webrtc::RTCErrorType::OPERATION_ERROR_WITH_DATA, - SctpErrorCauseCodeToString( - static_cast<SctpErrorCauseCode>(change.sac_error))); - error.set_error_detail(webrtc::RTCErrorDetailType::SCTP_FAILURE); - error.set_sctp_cause_code(change.sac_error); - SignalClosedAbruptly(error); - break; - } - case SCTP_RESTART: - RTC_LOG(LS_INFO) << "Association change SCTP_RESTART"; - break; - case SCTP_SHUTDOWN_COMP: - RTC_LOG(LS_INFO) << "Association change SCTP_SHUTDOWN_COMP"; - break; - case SCTP_CANT_STR_ASSOC: - RTC_LOG(LS_INFO) << "Association change SCTP_CANT_STR_ASSOC"; - break; - default: - RTC_LOG(LS_INFO) << "Association change UNKNOWN"; - break; - } -} - -void UsrsctpTransport::OnStreamResetEvent( - const struct sctp_stream_reset_event* evt) { - RTC_DCHECK_RUN_ON(network_thread_); - - // This callback indicates that a reset is complete for incoming and/or - // outgoing streams. The reset may have been initiated by us or the remote - // side. - const int num_sids = (evt->strreset_length - sizeof(*evt)) / - sizeof(evt->strreset_stream_list[0]); - - if (evt->strreset_flags & SCTP_STREAM_RESET_FAILED) { - // OK, just try sending any previously sent stream resets again. The stream - // IDs sent over when the RESET_FIALED flag is set seem to be garbage - // values. Ignore them. - for (std::map<uint32_t, StreamStatus>::value_type& stream : - stream_status_by_sid_) { - stream.second.outgoing_reset_initiated = false; - } - SendQueuedStreamResets(); - // TODO(deadbeef): If this happens, the entire SCTP association is in quite - // crippled state. The SCTP session should be dismantled, and the WebRTC - // connectivity errored because is clear that the distant party is not - // playing ball: malforms the transported data. - return; - } - - // Loop over the received events and properly update the StreamStatus map. - for (int i = 0; i < num_sids; i++) { - const uint32_t sid = evt->strreset_stream_list[i]; - auto it = stream_status_by_sid_.find(sid); - if (it == stream_status_by_sid_.end()) { - // This stream is unknown. Sometimes this can be from a - // RESET_FAILED-related retransmit. - RTC_LOG(LS_VERBOSE) << "SCTP_STREAM_RESET_EVENT(" << debug_name_ - << "): Unknown sid " << sid; - continue; - } - StreamStatus& status = it->second; - - if (evt->strreset_flags & SCTP_STREAM_RESET_INCOMING_SSN) { - RTC_LOG(LS_VERBOSE) << "SCTP_STREAM_RESET_INCOMING_SSN(" << debug_name_ - << "): sid " << sid; - status.incoming_reset_complete = true; - // If we receive an incoming stream reset and we haven't started the - // closing procedure ourselves, this means the remote side started the - // closing procedure; fire a signal so that the relevant data channel - // can change to "closing" (we still need to reset the outgoing stream - // before it changes to "closed"). - if (!status.closure_initiated) { - SignalClosingProcedureStartedRemotely(sid); - } - } - if (evt->strreset_flags & SCTP_STREAM_RESET_OUTGOING_SSN) { - RTC_LOG(LS_VERBOSE) << "SCTP_STREAM_RESET_OUTGOING_SSN(" << debug_name_ - << "): sid " << sid; - status.outgoing_reset_complete = true; - } - - // If this reset completes the closing procedure, remove the stream from - // our map so we can consider it closed, and fire a signal such that the - // relevant DataChannel will change its state to "closed" and its ID can be - // re-used. - if (status.reset_complete()) { - stream_status_by_sid_.erase(it); - SignalClosingProcedureComplete(sid); - } - } - - // Always try to send any queued resets because this call indicates that the - // last outgoing or incoming reset has made some progress. - SendQueuedStreamResets(); -} - -} // namespace cricket diff --git a/media/sctp/usrsctp_transport.h b/media/sctp/usrsctp_transport.h deleted file mode 100644 index 7c7ce8c4a8..0000000000 --- a/media/sctp/usrsctp_transport.h +++ /dev/null @@ -1,296 +0,0 @@ -/* - * Copyright (c) 2012 The WebRTC project authors. All Rights Reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#ifndef MEDIA_SCTP_USRSCTP_TRANSPORT_H_ -#define MEDIA_SCTP_USRSCTP_TRANSPORT_H_ - -#include <errno.h> - -#include <cstdint> -#include <map> -#include <memory> -#include <set> -#include <string> -#include <vector> - -#include "absl/types/optional.h" -#include "rtc_base/buffer.h" -#include "rtc_base/copy_on_write_buffer.h" -#include "rtc_base/task_utils/pending_task_safety_flag.h" -#include "rtc_base/third_party/sigslot/sigslot.h" -#include "rtc_base/thread.h" -// For SendDataParams/ReceiveDataParams. -#include "media/base/media_channel.h" -#include "media/sctp/sctp_transport_internal.h" - -// Defined by "usrsctplib/usrsctp.h" -struct sockaddr_conn; -struct sctp_assoc_change; -struct sctp_rcvinfo; -struct sctp_stream_reset_event; -struct sctp_sendv_spa; - -// Defined by <sys/socket.h> -struct socket; -namespace cricket { - -// Holds data to be passed on to a transport. -struct SctpInboundPacket; - -// From transport calls, data flows like this: -// [network thread (although it can in princple be another thread)] -// 1. SctpTransport::SendData(data) -// 2. usrsctp_sendv(data) -// [network thread returns; sctp thread then calls the following] -// 3. OnSctpOutboundPacket(wrapped_data) -// [sctp thread returns having async invoked on the network thread] -// 4. SctpTransport::OnPacketFromSctpToNetwork(wrapped_data) -// 5. DtlsTransport::SendPacket(wrapped_data) -// 6. ... across network ... a packet is sent back ... -// 7. SctpTransport::OnPacketReceived(wrapped_data) -// 8. usrsctp_conninput(wrapped_data) -// [network thread returns; sctp thread then calls the following] -// 9. OnSctpInboundData(data) -// 10. SctpTransport::OnDataFromSctpToTransport(data) -// [sctp thread returns having async invoked on the network thread] -// 11. SctpTransport::OnDataFromSctpToTransport(data) -// 12. SctpTransport::SignalDataReceived(data) -// [from the same thread, methods registered/connected to -// SctpTransport are called with the received data] -class UsrsctpTransport : public SctpTransportInternal, - public sigslot::has_slots<> { - public: - // `network_thread` is where packets will be processed and callbacks from - // this transport will be posted, and is the only thread on which public - // methods can be called. - // `transport` is not required (can be null). - UsrsctpTransport(rtc::Thread* network_thread, - rtc::PacketTransportInternal* transport); - ~UsrsctpTransport() override; - - UsrsctpTransport(const UsrsctpTransport&) = delete; - UsrsctpTransport& operator=(const UsrsctpTransport&) = delete; - - // SctpTransportInternal overrides (see sctptransportinternal.h for comments). - void SetDtlsTransport(rtc::PacketTransportInternal* transport) override; - bool Start(int local_port, int remote_port, int max_message_size) override; - bool OpenStream(int sid) override; - bool ResetStream(int sid) override; - bool SendData(int sid, - const webrtc::SendDataParams& params, - const rtc::CopyOnWriteBuffer& payload, - SendDataResult* result = nullptr) override; - bool ReadyToSendData() override; - int max_message_size() const override { return max_message_size_; } - absl::optional<int> max_outbound_streams() const override { - return max_outbound_streams_; - } - absl::optional<int> max_inbound_streams() const override { - return max_inbound_streams_; - } - void set_debug_name_for_testing(const char* debug_name) override { - debug_name_ = debug_name; - } - void InjectDataOrNotificationFromSctpForTesting(const void* data, - size_t length, - struct sctp_rcvinfo rcv, - int flags); - - // Exposed to allow Post call from c-callbacks. - // TODO(deadbeef): Remove this or at least make it return a const pointer. - rtc::Thread* network_thread() const { return network_thread_; } - - private: - // A message to be sent by the sctp library. This class is used to track the - // progress of writing a single message to the sctp library in the presence of - // partial writes. In this case, the Advance() function is provided in order - // to advance over what has already been accepted by the sctp library and - // avoid copying the remaining partial message buffer. - class OutgoingMessage { - public: - OutgoingMessage(const rtc::CopyOnWriteBuffer& buffer, - int sid, - const webrtc::SendDataParams& send_params) - : buffer_(buffer), sid_(sid), send_params_(send_params) {} - - // Advances the buffer by the incremented amount. Must not advance further - // than the current data size. - void Advance(size_t increment) { - RTC_DCHECK_LE(increment + offset_, buffer_.size()); - offset_ += increment; - } - - size_t size() const { return buffer_.size() - offset_; } - - const void* data() const { return buffer_.data() + offset_; } - - int sid() const { return sid_; } - webrtc::SendDataParams send_params() const { return send_params_; } - - private: - const rtc::CopyOnWriteBuffer buffer_; - int sid_; - const webrtc::SendDataParams send_params_; - size_t offset_ = 0; - }; - - void ConnectTransportSignals(); - void DisconnectTransportSignals(); - - // Creates the socket and connects. - bool Connect(); - - // Returns false when opening the socket failed. - bool OpenSctpSocket(); - // Helpet method to set socket options. - bool ConfigureSctpSocket(); - // Sets |sock_ |to nullptr. - void CloseSctpSocket(); - - // Sends a SCTP_RESET_STREAM for all streams in closing_ssids_. - bool SendQueuedStreamResets(); - - // Sets the "ready to send" flag and fires signal if needed. - void SetReadyToSendData(); - - // Sends the outgoing buffered message that was only partially accepted by the - // sctp lib because it did not have enough space. Returns true if the entire - // buffered message was accepted by the sctp lib. - bool SendBufferedMessage(); - - // Tries to send the `payload` on the usrsctp lib. The message will be - // advanced by the amount that was sent. - SendDataResult SendMessageInternal(OutgoingMessage* message); - - // Callbacks from DTLS transport. - void OnWritableState(rtc::PacketTransportInternal* transport); - virtual void OnPacketRead(rtc::PacketTransportInternal* transport, - const char* data, - size_t len, - const int64_t& packet_time_us, - int flags); - void OnClosed(rtc::PacketTransportInternal* transport); - - // Methods related to usrsctp callbacks. - void OnSendThresholdCallback(); - sockaddr_conn GetSctpSockAddr(int port); - - // Called using `invoker_` to send packet on the network. - void OnPacketFromSctpToNetwork(const rtc::CopyOnWriteBuffer& buffer); - - // Called on the network thread. - // Flags are standard socket API flags (RFC 6458). - void OnDataOrNotificationFromSctp(const void* data, - size_t length, - struct sctp_rcvinfo rcv, - int flags); - // Called using `invoker_` to decide what to do with the data. - void OnDataFromSctpToTransport(const ReceiveDataParams& params, - const rtc::CopyOnWriteBuffer& buffer); - // Called using `invoker_` to decide what to do with the notification. - void OnNotificationFromSctp(const rtc::CopyOnWriteBuffer& buffer); - void OnNotificationAssocChange(const sctp_assoc_change& change); - - void OnStreamResetEvent(const struct sctp_stream_reset_event* evt); - - // Responsible for marshalling incoming data to the transports listeners, and - // outgoing data to the network interface. - rtc::Thread* network_thread_; - // Helps pass inbound/outbound packets asynchronously to the network thread. - webrtc::ScopedTaskSafety task_safety_; - // Underlying DTLS transport. - rtc::PacketTransportInternal* transport_ = nullptr; - - // Track the data received from usrsctp between callbacks until the EOR bit - // arrives. - rtc::CopyOnWriteBuffer partial_incoming_message_; - ReceiveDataParams partial_params_; - int partial_flags_; - // A message that was attempted to be sent, but was only partially accepted by - // usrsctp lib with usrsctp_sendv() because it cannot buffer the full message. - // This occurs because we explicitly set the EOR bit when sending, so - // usrsctp_sendv() is not atomic. - absl::optional<OutgoingMessage> partial_outgoing_message_; - - bool was_ever_writable_ = false; - int local_port_ = kSctpDefaultPort; - int remote_port_ = kSctpDefaultPort; - int max_message_size_ = kSctpSendBufferSize; - struct socket* sock_ = nullptr; // The socket created by usrsctp_socket(...). - - // Has Start been called? Don't create SCTP socket until it has. - bool started_ = false; - // Are we ready to queue data (SCTP socket created, and not blocked due to - // congestion control)? Different than `transport_`'s "ready to send". - bool ready_to_send_data_ = false; - - // Used to keep track of the status of each stream (or rather, each pair of - // incoming/outgoing streams with matching IDs). It's specifically used to - // keep track of the status of resets, but more information could be put here - // later. - // - // See datachannel.h for a summary of the closing procedure. - struct StreamStatus { - // Closure initiated by application via ResetStream? Note that - // this may be true while outgoing_reset_initiated is false if the outgoing - // reset needed to be queued. - bool closure_initiated = false; - // Whether we've initiated the outgoing stream reset via - // SCTP_RESET_STREAMS. - bool outgoing_reset_initiated = false; - // Whether usrsctp has indicated that the incoming/outgoing streams have - // been reset. It's expected that the peer will reset its outgoing stream - // (our incoming stream) after receiving the reset for our outgoing stream, - // though older versions of chromium won't do this. See crbug.com/559394 - // for context. - bool outgoing_reset_complete = false; - bool incoming_reset_complete = false; - - // Some helper methods to improve code readability. - bool is_open() const { - return !closure_initiated && !incoming_reset_complete && - !outgoing_reset_complete; - } - // We need to send an outgoing reset if the application has closed the data - // channel, or if we received a reset of the incoming stream from the - // remote endpoint, indicating the data channel was closed remotely. - bool need_outgoing_reset() const { - return (incoming_reset_complete || closure_initiated) && - !outgoing_reset_initiated; - } - bool reset_complete() const { - return outgoing_reset_complete && incoming_reset_complete; - } - }; - - // Entries should only be removed from this map if `reset_complete` is - // true. - std::map<uint32_t, StreamStatus> stream_status_by_sid_; - - // A static human-readable name for debugging messages. - const char* debug_name_ = "UsrsctpTransport"; - // Hides usrsctp interactions from this header file. - class UsrSctpWrapper; - // Number of channels negotiated. Not set before negotiation completes. - absl::optional<int> max_outbound_streams_; - absl::optional<int> max_inbound_streams_; - - // Used for associating this transport with the underlying sctp socket in - // various callbacks. - uintptr_t id_ = 0; - - friend class UsrsctpTransportMap; -}; - -class UsrsctpTransportMap; - -} // namespace cricket - -#endif // MEDIA_SCTP_USRSCTP_TRANSPORT_H_ diff --git a/media/sctp/usrsctp_transport_reliability_unittest.cc b/media/sctp/usrsctp_transport_reliability_unittest.cc deleted file mode 100644 index 987dd04358..0000000000 --- a/media/sctp/usrsctp_transport_reliability_unittest.cc +++ /dev/null @@ -1,809 +0,0 @@ -/* - * Copyright (c) 2019 The WebRTC project authors. All Rights Reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ -#include <memory> -#include <queue> -#include <string> - -#include "media/sctp/sctp_transport_internal.h" -#include "media/sctp/usrsctp_transport.h" -#include "rtc_base/copy_on_write_buffer.h" -#include "rtc_base/event.h" -#include "rtc_base/gunit.h" -#include "rtc_base/logging.h" -#include "rtc_base/random.h" -#include "rtc_base/synchronization/mutex.h" -#include "rtc_base/task_utils/pending_task_safety_flag.h" -#include "rtc_base/task_utils/to_queued_task.h" -#include "rtc_base/thread.h" -#include "test/gtest.h" - -namespace { - -static constexpr int kDefaultTimeout = 10000; // 10 seconds. -static constexpr int kTransport1Port = 15001; -static constexpr int kTransport2Port = 25002; -static constexpr int kLogPerMessagesCount = 100; - -/** - * An simple packet transport implementation which can be - * configured to simulate uniform random packet loss and - * configurable random packet delay and reordering. - */ -class SimulatedPacketTransport final : public rtc::PacketTransportInternal { - public: - SimulatedPacketTransport(std::string name, - rtc::Thread* transport_thread, - uint8_t packet_loss_percents, - uint16_t avg_send_delay_millis) - : transport_name_(name), - transport_thread_(transport_thread), - packet_loss_percents_(packet_loss_percents), - avg_send_delay_millis_(avg_send_delay_millis), - random_(42) { - RTC_DCHECK(transport_thread_); - RTC_DCHECK_LE(packet_loss_percents_, 100); - RTC_DCHECK_RUN_ON(transport_thread_); - } - - ~SimulatedPacketTransport() override { - RTC_DCHECK_RUN_ON(transport_thread_); - destination_ = nullptr; - SignalWritableState(this); - } - - SimulatedPacketTransport(const SimulatedPacketTransport&) = delete; - SimulatedPacketTransport& operator=(const SimulatedPacketTransport&) = delete; - - const std::string& transport_name() const override { return transport_name_; } - - bool writable() const override { return destination_ != nullptr; } - - bool receiving() const override { return true; } - - int SendPacket(const char* data, - size_t len, - const rtc::PacketOptions& options, - int flags = 0) { - RTC_DCHECK_RUN_ON(transport_thread_); - auto destination = destination_.load(); - if (destination == nullptr) { - return -1; - } - if (random_.Rand(100) < packet_loss_percents_) { - // silent packet loss - return 0; - } - rtc::CopyOnWriteBuffer buffer(data, len); - auto send_task = ToQueuedTask( - destination->task_safety_.flag(), - [destination, flags, buffer = std::move(buffer)] { - destination->SignalReadPacket( - destination, reinterpret_cast<const char*>(buffer.data()), - buffer.size(), rtc::Time(), flags); - }); - // Introduce random send delay in range [0 .. 2 * avg_send_delay_millis_] - // millis, which will also work as random packet reordering mechanism. - uint16_t actual_send_delay = avg_send_delay_millis_; - int16_t reorder_delay = - avg_send_delay_millis_ * - std::min(1.0, std::max(-1.0, random_.Gaussian(0, 0.5))); - actual_send_delay += reorder_delay; - - if (actual_send_delay > 0) { - destination->transport_thread_->PostDelayedTask(std::move(send_task), - actual_send_delay); - } else { - destination->transport_thread_->PostTask(std::move(send_task)); - } - return 0; - } - - int SetOption(rtc::Socket::Option opt, int value) override { return 0; } - - bool GetOption(rtc::Socket::Option opt, int* value) override { return false; } - - int GetError() override { return 0; } - - absl::optional<rtc::NetworkRoute> network_route() const override { - return absl::nullopt; - } - - void SetDestination(SimulatedPacketTransport* destination) { - RTC_DCHECK_RUN_ON(transport_thread_); - if (destination == this) { - return; - } - destination_ = destination; - SignalWritableState(this); - } - - private: - const std::string transport_name_; - rtc::Thread* const transport_thread_; - const uint8_t packet_loss_percents_; - const uint16_t avg_send_delay_millis_; - std::atomic<SimulatedPacketTransport*> destination_ ATOMIC_VAR_INIT(nullptr); - webrtc::Random random_; - webrtc::ScopedTaskSafety task_safety_; -}; - -/** - * A helper class to send specified number of messages over UsrsctpTransport - * with SCTP reliability settings provided by user. The reliability settings are - * specified by passing a template instance of SendDataParams. The sid will be - * assigned by sender itself and will be assigned from range - * [cricket::kMinSctpSid; cricket::kMaxSctpSid]. The wide range of sids are used - * to possibly trigger more execution paths inside usrsctp. - */ -class SctpDataSender final { - public: - SctpDataSender(rtc::Thread* thread, - cricket::UsrsctpTransport* transport, - uint64_t target_messages_count, - webrtc::SendDataParams send_params, - uint32_t sender_id) - : thread_(thread), - transport_(transport), - target_messages_count_(target_messages_count), - send_params_(send_params), - sender_id_(sender_id) { - RTC_DCHECK(thread_); - RTC_DCHECK(transport_); - } - - SctpDataSender(const SctpDataSender&) = delete; - SctpDataSender& operator=(const SctpDataSender&) = delete; - - void Start() { - thread_->PostTask(ToQueuedTask(task_safety_.flag(), [this] { - if (started_) { - RTC_LOG(LS_INFO) << sender_id_ << " sender is already started"; - return; - } - started_ = true; - SendNextMessage(); - })); - } - - uint64_t BytesSentCount() const { return num_bytes_sent_; } - - uint64_t MessagesSentCount() const { return num_messages_sent_; } - - absl::optional<std::string> GetLastError() { - absl::optional<std::string> result = absl::nullopt; - thread_->Invoke<void>(RTC_FROM_HERE, - [this, &result] { result = last_error_; }); - return result; - } - - bool WaitForCompletion(int give_up_after_ms) { - return sent_target_messages_count_.Wait(give_up_after_ms, kDefaultTimeout); - } - - private: - void SendNextMessage() { - RTC_DCHECK_RUN_ON(thread_); - if (!started_ || num_messages_sent_ >= target_messages_count_) { - sent_target_messages_count_.Set(); - return; - } - - if (num_messages_sent_ % kLogPerMessagesCount == 0) { - RTC_LOG(LS_INFO) << sender_id_ << " sender will try send message " - << (num_messages_sent_ + 1) << " out of " - << target_messages_count_; - } - - webrtc::SendDataParams params(send_params_); - int sid = - cricket::kMinSctpSid + (num_messages_sent_ % cricket::kMaxSctpStreams); - - cricket::SendDataResult result; - transport_->SendData(sid, params, payload_, &result); - switch (result) { - case cricket::SDR_BLOCK: - // retry after timeout - thread_->PostDelayedTask( - ToQueuedTask(task_safety_.flag(), [this] { SendNextMessage(); }), - 500); - break; - case cricket::SDR_SUCCESS: - // send next - num_bytes_sent_ += payload_.size(); - ++num_messages_sent_; - thread_->PostTask( - ToQueuedTask(task_safety_.flag(), [this] { SendNextMessage(); })); - break; - case cricket::SDR_ERROR: - // give up - last_error_ = "UsrsctpTransport::SendData error returned"; - sent_target_messages_count_.Set(); - break; - } - } - - rtc::Thread* const thread_; - cricket::UsrsctpTransport* const transport_; - const uint64_t target_messages_count_; - const webrtc::SendDataParams send_params_; - const uint32_t sender_id_; - rtc::CopyOnWriteBuffer payload_{std::string(1400, '.').c_str(), 1400}; - std::atomic<bool> started_ ATOMIC_VAR_INIT(false); - std::atomic<uint64_t> num_messages_sent_ ATOMIC_VAR_INIT(0); - rtc::Event sent_target_messages_count_{true, false}; - std::atomic<uint64_t> num_bytes_sent_ ATOMIC_VAR_INIT(0); - absl::optional<std::string> last_error_; - webrtc::ScopedTaskSafetyDetached task_safety_; -}; - -/** - * A helper class which counts number of received messages - * and bytes over UsrsctpTransport. Also allow waiting until - * specified number of messages received. - */ -class SctpDataReceiver final : public sigslot::has_slots<> { - public: - explicit SctpDataReceiver(uint32_t receiver_id, - uint64_t target_messages_count) - : receiver_id_(receiver_id), - target_messages_count_(target_messages_count) {} - - SctpDataReceiver(const SctpDataReceiver&) = delete; - SctpDataReceiver& operator=(const SctpDataReceiver&) = delete; - - void OnDataReceived(const cricket::ReceiveDataParams& params, - const rtc::CopyOnWriteBuffer& data) { - num_bytes_received_ += data.size(); - if (++num_messages_received_ == target_messages_count_) { - received_target_messages_count_.Set(); - } - - if (num_messages_received_ % kLogPerMessagesCount == 0) { - RTC_LOG(LS_INFO) << receiver_id_ << " receiver got " - << num_messages_received_ << " messages"; - } - } - - uint64_t MessagesReceivedCount() const { return num_messages_received_; } - - uint64_t BytesReceivedCount() const { return num_bytes_received_; } - - bool WaitForMessagesReceived(int timeout_millis) { - return received_target_messages_count_.Wait(timeout_millis); - } - - private: - std::atomic<uint64_t> num_messages_received_ ATOMIC_VAR_INIT(0); - std::atomic<uint64_t> num_bytes_received_ ATOMIC_VAR_INIT(0); - rtc::Event received_target_messages_count_{true, false}; - const uint32_t receiver_id_; - const uint64_t target_messages_count_; -}; - -/** - * Simple class to manage set of threads. - */ -class ThreadPool final { - public: - explicit ThreadPool(size_t threads_count) : random_(42) { - RTC_DCHECK(threads_count > 0); - threads_.reserve(threads_count); - for (size_t i = 0; i < threads_count; i++) { - auto thread = rtc::Thread::Create(); - thread->SetName("Thread #" + rtc::ToString(i + 1) + " from Pool", this); - thread->Start(); - threads_.emplace_back(std::move(thread)); - } - } - - ThreadPool(const ThreadPool&) = delete; - ThreadPool& operator=(const ThreadPool&) = delete; - - rtc::Thread* GetRandomThread() { - return threads_[random_.Rand(0U, threads_.size() - 1)].get(); - } - - private: - webrtc::Random random_; - std::vector<std::unique_ptr<rtc::Thread>> threads_; -}; - -/** - * Represents single ping-pong test over UsrsctpTransport. - * User can specify target number of message for bidirectional - * send, underlying transport packets loss and average packet delay - * and SCTP delivery settings. - */ -class SctpPingPong final { - public: - SctpPingPong(uint32_t id, - uint16_t port1, - uint16_t port2, - rtc::Thread* transport_thread1, - rtc::Thread* transport_thread2, - uint32_t messages_count, - uint8_t packet_loss_percents, - uint16_t avg_send_delay_millis, - webrtc::SendDataParams send_params) - : id_(id), - port1_(port1), - port2_(port2), - transport_thread1_(transport_thread1), - transport_thread2_(transport_thread2), - messages_count_(messages_count), - packet_loss_percents_(packet_loss_percents), - avg_send_delay_millis_(avg_send_delay_millis), - send_params_(send_params) { - RTC_DCHECK(transport_thread1_ != nullptr); - RTC_DCHECK(transport_thread2_ != nullptr); - } - - virtual ~SctpPingPong() { - transport_thread1_->Invoke<void>(RTC_FROM_HERE, [this] { - data_sender1_.reset(); - sctp_transport1_->SetDtlsTransport(nullptr); - packet_transport1_->SetDestination(nullptr); - }); - transport_thread2_->Invoke<void>(RTC_FROM_HERE, [this] { - data_sender2_.reset(); - sctp_transport2_->SetDtlsTransport(nullptr); - packet_transport2_->SetDestination(nullptr); - }); - transport_thread1_->Invoke<void>(RTC_FROM_HERE, [this] { - sctp_transport1_.reset(); - data_receiver1_.reset(); - packet_transport1_.reset(); - }); - transport_thread2_->Invoke<void>(RTC_FROM_HERE, [this] { - sctp_transport2_.reset(); - data_receiver2_.reset(); - packet_transport2_.reset(); - }); - } - - SctpPingPong(const SctpPingPong&) = delete; - SctpPingPong& operator=(const SctpPingPong&) = delete; - - bool Start() { - CreateTwoConnectedSctpTransportsWithAllStreams(); - - { - webrtc::MutexLock lock(&lock_); - if (!errors_list_.empty()) { - return false; - } - } - - data_sender1_.reset(new SctpDataSender(transport_thread1_, - sctp_transport1_.get(), - messages_count_, send_params_, id_)); - data_sender2_.reset(new SctpDataSender(transport_thread2_, - sctp_transport2_.get(), - messages_count_, send_params_, id_)); - data_sender1_->Start(); - data_sender2_->Start(); - return true; - } - - std::vector<std::string> GetErrorsList() const { - std::vector<std::string> result; - { - webrtc::MutexLock lock(&lock_); - result = errors_list_; - } - return result; - } - - void WaitForCompletion(int32_t timeout_millis) { - if (data_sender1_ == nullptr) { - ReportError("SctpPingPong id = " + rtc::ToString(id_) + - ", sender 1 is not created"); - return; - } - if (data_sender2_ == nullptr) { - ReportError("SctpPingPong id = " + rtc::ToString(id_) + - ", sender 2 is not created"); - return; - } - - if (!data_sender1_->WaitForCompletion(timeout_millis)) { - ReportError("SctpPingPong id = " + rtc::ToString(id_) + - ", sender 1 failed to complete within " + - rtc::ToString(timeout_millis) + " millis"); - return; - } - - auto sender1_error = data_sender1_->GetLastError(); - if (sender1_error.has_value()) { - ReportError("SctpPingPong id = " + rtc::ToString(id_) + - ", sender 1 error: " + sender1_error.value()); - return; - } - - if (!data_sender2_->WaitForCompletion(timeout_millis)) { - ReportError("SctpPingPong id = " + rtc::ToString(id_) + - ", sender 2 failed to complete within " + - rtc::ToString(timeout_millis) + " millis"); - return; - } - - auto sender2_error = data_sender2_->GetLastError(); - if (sender2_error.has_value()) { - ReportError("SctpPingPong id = " + rtc::ToString(id_) + - ", sender 2 error: " + sender1_error.value()); - return; - } - - if ((data_sender1_->MessagesSentCount() != messages_count_)) { - ReportError("SctpPingPong id = " + rtc::ToString(id_) + - ", sender 1 sent only " + - rtc::ToString(data_sender1_->MessagesSentCount()) + - " out of " + rtc::ToString(messages_count_)); - return; - } - - if ((data_sender2_->MessagesSentCount() != messages_count_)) { - ReportError("SctpPingPong id = " + rtc::ToString(id_) + - ", sender 2 sent only " + - rtc::ToString(data_sender2_->MessagesSentCount()) + - " out of " + rtc::ToString(messages_count_)); - return; - } - - if (!data_receiver1_->WaitForMessagesReceived(timeout_millis)) { - ReportError("SctpPingPong id = " + rtc::ToString(id_) + - ", receiver 1 did not complete within " + - rtc::ToString(messages_count_)); - return; - } - - if (!data_receiver2_->WaitForMessagesReceived(timeout_millis)) { - ReportError("SctpPingPong id = " + rtc::ToString(id_) + - ", receiver 2 did not complete within " + - rtc::ToString(messages_count_)); - return; - } - - if (data_receiver1_->BytesReceivedCount() != - data_sender2_->BytesSentCount()) { - ReportError( - "SctpPingPong id = " + rtc::ToString(id_) + ", receiver 1 received " + - rtc::ToString(data_receiver1_->BytesReceivedCount()) + - " bytes, but sender 2 send " + - rtc::ToString(rtc::ToString(data_sender2_->BytesSentCount()))); - return; - } - - if (data_receiver2_->BytesReceivedCount() != - data_sender1_->BytesSentCount()) { - ReportError( - "SctpPingPong id = " + rtc::ToString(id_) + ", receiver 2 received " + - rtc::ToString(data_receiver2_->BytesReceivedCount()) + - " bytes, but sender 1 send " + - rtc::ToString(rtc::ToString(data_sender1_->BytesSentCount()))); - return; - } - - RTC_LOG(LS_INFO) << "SctpPingPong id = " << id_ << " is done"; - } - - private: - void CreateTwoConnectedSctpTransportsWithAllStreams() { - transport_thread1_->Invoke<void>(RTC_FROM_HERE, [this] { - packet_transport1_.reset(new SimulatedPacketTransport( - "SctpPingPong id = " + rtc::ToString(id_) + ", packet transport 1", - transport_thread1_, packet_loss_percents_, avg_send_delay_millis_)); - data_receiver1_.reset(new SctpDataReceiver(id_, messages_count_)); - sctp_transport1_.reset(new cricket::UsrsctpTransport( - transport_thread1_, packet_transport1_.get())); - sctp_transport1_->set_debug_name_for_testing("sctp transport 1"); - - sctp_transport1_->SignalDataReceived.connect( - data_receiver1_.get(), &SctpDataReceiver::OnDataReceived); - - for (uint32_t i = cricket::kMinSctpSid; i <= cricket::kMaxSctpSid; i++) { - if (!sctp_transport1_->OpenStream(i)) { - ReportError("SctpPingPong id = " + rtc::ToString(id_) + - ", sctp transport 1 stream " + rtc::ToString(i) + - " failed to open"); - break; - } - } - }); - - transport_thread2_->Invoke<void>(RTC_FROM_HERE, [this] { - packet_transport2_.reset(new SimulatedPacketTransport( - "SctpPingPong id = " + rtc::ToString(id_) + "packet transport 2", - transport_thread2_, packet_loss_percents_, avg_send_delay_millis_)); - data_receiver2_.reset(new SctpDataReceiver(id_, messages_count_)); - sctp_transport2_.reset(new cricket::UsrsctpTransport( - transport_thread2_, packet_transport2_.get())); - sctp_transport2_->set_debug_name_for_testing("sctp transport 2"); - sctp_transport2_->SignalDataReceived.connect( - data_receiver2_.get(), &SctpDataReceiver::OnDataReceived); - - for (uint32_t i = cricket::kMinSctpSid; i <= cricket::kMaxSctpSid; i++) { - if (!sctp_transport2_->OpenStream(i)) { - ReportError("SctpPingPong id = " + rtc::ToString(id_) + - ", sctp transport 2 stream " + rtc::ToString(i) + - " failed to open"); - break; - } - } - }); - - transport_thread1_->Invoke<void>(RTC_FROM_HERE, [this] { - packet_transport1_->SetDestination(packet_transport2_.get()); - }); - transport_thread2_->Invoke<void>(RTC_FROM_HERE, [this] { - packet_transport2_->SetDestination(packet_transport1_.get()); - }); - - transport_thread1_->Invoke<void>(RTC_FROM_HERE, [this] { - if (!sctp_transport1_->Start(port1_, port2_, - cricket::kSctpSendBufferSize)) { - ReportError("SctpPingPong id = " + rtc::ToString(id_) + - ", failed to start sctp transport 1"); - } - }); - - transport_thread2_->Invoke<void>(RTC_FROM_HERE, [this] { - if (!sctp_transport2_->Start(port2_, port1_, - cricket::kSctpSendBufferSize)) { - ReportError("SctpPingPong id = " + rtc::ToString(id_) + - ", failed to start sctp transport 2"); - } - }); - } - - void ReportError(std::string error) { - webrtc::MutexLock lock(&lock_); - errors_list_.push_back(std::move(error)); - } - - std::unique_ptr<SimulatedPacketTransport> packet_transport1_; - std::unique_ptr<SimulatedPacketTransport> packet_transport2_; - std::unique_ptr<SctpDataReceiver> data_receiver1_; - std::unique_ptr<SctpDataReceiver> data_receiver2_; - std::unique_ptr<cricket::UsrsctpTransport> sctp_transport1_; - std::unique_ptr<cricket::UsrsctpTransport> sctp_transport2_; - std::unique_ptr<SctpDataSender> data_sender1_; - std::unique_ptr<SctpDataSender> data_sender2_; - mutable webrtc::Mutex lock_; - std::vector<std::string> errors_list_ RTC_GUARDED_BY(lock_); - - const uint32_t id_; - const uint16_t port1_; - const uint16_t port2_; - rtc::Thread* const transport_thread1_; - rtc::Thread* const transport_thread2_; - const uint32_t messages_count_; - const uint8_t packet_loss_percents_; - const uint16_t avg_send_delay_millis_; - const webrtc::SendDataParams send_params_; -}; - -/** - * Helper function to calculate max number of milliseconds - * allowed for test to run based on test configuration. - */ -constexpr int32_t GetExecutionTimeLimitInMillis(uint32_t total_messages, - uint8_t packet_loss_percents) { - return std::min<int64_t>( - std::numeric_limits<int32_t>::max(), - std::max<int64_t>( - 1LL * total_messages * 100 * - std::max(1, packet_loss_percents * packet_loss_percents), - kDefaultTimeout)); -} - -} // namespace - -namespace cricket { - -/** - * The set of tests intended to check usrsctp reliability on - * stress conditions: multiple sockets, concurrent access, - * lossy network link. It was observed in the past that - * usrsctp might misbehave in concurrent environment - * under load on lossy networks: deadlocks and memory corruption - * issues might happen in non-basic usage scenarios. - * It's recommended to run this test whenever usrsctp version - * used is updated to verify it properly works in stress - * conditions under higher than usual load. - * It is also recommended to enable ASAN when these tests - * are executed, so whenever memory bug is happen inside usrsctp, - * it will be easier to understand what went wrong with ASAN - * provided diagnostics information. - * The tests cases currently disabled by default due to - * long execution time and due to unresolved issue inside - * `usrsctp` library detected by try-bots with ThreadSanitizer. - */ -class UsrSctpReliabilityTest : public ::testing::Test {}; - -/** - * A simple test which send multiple messages over reliable - * connection, usefull to verify test infrastructure works. - * Execution time is less than 1 second. - */ -TEST_F(UsrSctpReliabilityTest, - DISABLED_AllMessagesAreDeliveredOverReliableConnection) { - auto thread1 = rtc::Thread::Create(); - auto thread2 = rtc::Thread::Create(); - thread1->Start(); - thread2->Start(); - constexpr uint8_t packet_loss_percents = 0; - constexpr uint16_t avg_send_delay_millis = 10; - constexpr uint32_t messages_count = 100; - constexpr int32_t wait_timeout = - GetExecutionTimeLimitInMillis(messages_count, packet_loss_percents); - static_assert(wait_timeout > 0, - "Timeout computation must produce positive value"); - - webrtc::SendDataParams send_params; - send_params.ordered = true; - - SctpPingPong test(1, kTransport1Port, kTransport2Port, thread1.get(), - thread2.get(), messages_count, packet_loss_percents, - avg_send_delay_millis, send_params); - EXPECT_TRUE(test.Start()) << rtc::join(test.GetErrorsList(), ';'); - test.WaitForCompletion(wait_timeout); - auto errors_list = test.GetErrorsList(); - EXPECT_TRUE(errors_list.empty()) << rtc::join(errors_list, ';'); -} - -/** - * A test to verify that multiple messages can be reliably delivered - * over lossy network when usrsctp configured to guarantee reliably - * and in order delivery. - * The test case is disabled by default because it takes - * long time to run. - * Execution time is about 2.5 minutes. - */ -TEST_F(UsrSctpReliabilityTest, - DISABLED_AllMessagesAreDeliveredOverLossyConnectionReliableAndInOrder) { - auto thread1 = rtc::Thread::Create(); - auto thread2 = rtc::Thread::Create(); - thread1->Start(); - thread2->Start(); - constexpr uint8_t packet_loss_percents = 5; - constexpr uint16_t avg_send_delay_millis = 16; - constexpr uint32_t messages_count = 10000; - constexpr int32_t wait_timeout = - GetExecutionTimeLimitInMillis(messages_count, packet_loss_percents); - static_assert(wait_timeout > 0, - "Timeout computation must produce positive value"); - - webrtc::SendDataParams send_params; - send_params.ordered = true; - - SctpPingPong test(1, kTransport1Port, kTransport2Port, thread1.get(), - thread2.get(), messages_count, packet_loss_percents, - avg_send_delay_millis, send_params); - - EXPECT_TRUE(test.Start()) << rtc::join(test.GetErrorsList(), ';'); - test.WaitForCompletion(wait_timeout); - auto errors_list = test.GetErrorsList(); - EXPECT_TRUE(errors_list.empty()) << rtc::join(errors_list, ';'); -} - -/** - * A test to verify that multiple messages can be reliably delivered - * over lossy network when usrsctp configured to retransmit lost - * packets. - * The test case is disabled by default because it takes - * long time to run. - * Execution time is about 2.5 minutes. - */ -TEST_F(UsrSctpReliabilityTest, - DISABLED_AllMessagesAreDeliveredOverLossyConnectionWithRetries) { - auto thread1 = rtc::Thread::Create(); - auto thread2 = rtc::Thread::Create(); - thread1->Start(); - thread2->Start(); - constexpr uint8_t packet_loss_percents = 5; - constexpr uint16_t avg_send_delay_millis = 16; - constexpr uint32_t messages_count = 10000; - constexpr int32_t wait_timeout = - GetExecutionTimeLimitInMillis(messages_count, packet_loss_percents); - static_assert(wait_timeout > 0, - "Timeout computation must produce positive value"); - - webrtc::SendDataParams send_params; - send_params.ordered = false; - send_params.max_rtx_count = std::numeric_limits<uint16_t>::max(); - send_params.max_rtx_ms = std::numeric_limits<uint16_t>::max(); - - SctpPingPong test(1, kTransport1Port, kTransport2Port, thread1.get(), - thread2.get(), messages_count, packet_loss_percents, - avg_send_delay_millis, send_params); - - EXPECT_TRUE(test.Start()) << rtc::join(test.GetErrorsList(), ';'); - test.WaitForCompletion(wait_timeout); - auto errors_list = test.GetErrorsList(); - EXPECT_TRUE(errors_list.empty()) << rtc::join(errors_list, ';'); -} - -/** - * This is kind of reliability stress-test of usrsctp to verify - * that all messages are delivered when multiple usrsctp - * sockets used concurrently and underlying transport is lossy. - * - * It was observed in the past that in stress condtions usrsctp - * might encounter deadlock and memory corruption bugs: - * https://github.com/sctplab/usrsctp/issues/325 - * - * It is recoomended to run this test whenever usrsctp version - * used by WebRTC is updated. - * - * The test case is disabled by default because it takes - * long time to run. - * Execution time of this test is about 1-2 hours. - */ -TEST_F(UsrSctpReliabilityTest, - DISABLED_AllMessagesAreDeliveredOverLossyConnectionConcurrentTests) { - ThreadPool pool(16); - - webrtc::SendDataParams send_params; - send_params.ordered = true; - constexpr uint32_t base_sctp_port = 5000; - - // The constants value below were experimentally chosen - // to have reasonable execution time and to reproduce - // particular deadlock issue inside usrsctp: - // https://github.com/sctplab/usrsctp/issues/325 - // The constants values may be adjusted next time - // some other issue inside usrsctp need to be debugged. - constexpr uint32_t messages_count = 200; - constexpr uint8_t packet_loss_percents = 5; - constexpr uint16_t avg_send_delay_millis = 0; - constexpr uint32_t parallel_ping_pongs = 16 * 1024; - constexpr uint32_t total_ping_pong_tests = 16 * parallel_ping_pongs; - - constexpr int32_t wait_timeout = GetExecutionTimeLimitInMillis( - total_ping_pong_tests * messages_count, packet_loss_percents); - static_assert(wait_timeout > 0, - "Timeout computation must produce positive value"); - - std::queue<std::unique_ptr<SctpPingPong>> tests; - - for (uint32_t i = 0; i < total_ping_pong_tests; i++) { - uint32_t port1 = - base_sctp_port + (2 * i) % (UINT16_MAX - base_sctp_port - 1); - - auto test = std::make_unique<SctpPingPong>( - i, port1, port1 + 1, pool.GetRandomThread(), pool.GetRandomThread(), - messages_count, packet_loss_percents, avg_send_delay_millis, - send_params); - - EXPECT_TRUE(test->Start()) << rtc::join(test->GetErrorsList(), ';'); - tests.emplace(std::move(test)); - - while (tests.size() >= parallel_ping_pongs) { - auto& oldest_test = tests.front(); - oldest_test->WaitForCompletion(wait_timeout); - - auto errors_list = oldest_test->GetErrorsList(); - EXPECT_TRUE(errors_list.empty()) << rtc::join(errors_list, ';'); - tests.pop(); - } - } - - while (!tests.empty()) { - auto& oldest_test = tests.front(); - oldest_test->WaitForCompletion(wait_timeout); - - auto errors_list = oldest_test->GetErrorsList(); - EXPECT_TRUE(errors_list.empty()) << rtc::join(errors_list, ';'); - tests.pop(); - } -} - -} // namespace cricket diff --git a/media/sctp/usrsctp_transport_unittest.cc b/media/sctp/usrsctp_transport_unittest.cc deleted file mode 100644 index 8fdbabc14a..0000000000 --- a/media/sctp/usrsctp_transport_unittest.cc +++ /dev/null @@ -1,883 +0,0 @@ -/* - * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#include "media/sctp/usrsctp_transport.h" - -#include <stdio.h> -#include <string.h> -#include <usrsctp.h> - -#include <memory> -#include <string> -#include <vector> - -#include "absl/algorithm/container.h" -#include "media/sctp/sctp_transport_internal.h" -#include "p2p/base/fake_dtls_transport.h" -#include "rtc_base/copy_on_write_buffer.h" -#include "rtc_base/gunit.h" -#include "rtc_base/logging.h" -#include "rtc_base/thread.h" -#include "test/gtest.h" - -namespace { -static const int kDefaultTimeout = 10000; // 10 seconds. -// Use ports other than the default 5000 for testing. -static const int kTransport1Port = 5001; -static const int kTransport2Port = 5002; -} // namespace - -namespace cricket { - -// This is essentially a buffer to hold received data. It stores only the last -// received data. Calling OnDataReceived twice overwrites old data with the -// newer one. -// TODO(ldixon): Implement constraints, and allow new data to be added to old -// instead of replacing it. -class SctpFakeDataReceiver : public sigslot::has_slots<> { - public: - SctpFakeDataReceiver() : received_(false) {} - - void Clear() { - received_ = false; - last_data_ = ""; - last_params_ = ReceiveDataParams(); - num_messages_received_ = 0; - } - - void OnDataReceived(const ReceiveDataParams& params, - const rtc::CopyOnWriteBuffer& data) { - num_messages_received_++; - received_ = true; - last_data_ = std::string(data.data<char>(), data.size()); - last_params_ = params; - } - - bool received() const { return received_; } - std::string last_data() const { return last_data_; } - ReceiveDataParams last_params() const { return last_params_; } - size_t num_messages_received() const { return num_messages_received_; } - - private: - bool received_; - std::string last_data_; - size_t num_messages_received_ = 0; - ReceiveDataParams last_params_; -}; - -class SctpTransportObserver : public sigslot::has_slots<> { - public: - explicit SctpTransportObserver(UsrsctpTransport* transport) { - transport->SignalClosingProcedureComplete.connect( - this, &SctpTransportObserver::OnClosingProcedureComplete); - transport->SignalReadyToSendData.connect( - this, &SctpTransportObserver::OnReadyToSend); - } - - int StreamCloseCount(int stream) { - return absl::c_count(closed_streams_, stream); - } - - bool WasStreamClosed(int stream) { - return absl::c_linear_search(closed_streams_, stream); - } - - bool ReadyToSend() { return ready_to_send_; } - - private: - void OnClosingProcedureComplete(int stream) { - closed_streams_.push_back(stream); - } - void OnReadyToSend() { ready_to_send_ = true; } - - std::vector<int> closed_streams_; - bool ready_to_send_ = false; -}; - -// Helper class used to immediately attempt to reopen a stream as soon as it's -// been closed. -class SignalTransportClosedReopener : public sigslot::has_slots<> { - public: - SignalTransportClosedReopener(UsrsctpTransport* transport, - UsrsctpTransport* peer) - : transport_(transport), peer_(peer) {} - - int StreamCloseCount(int stream) { return absl::c_count(streams_, stream); } - - private: - void OnStreamClosed(int stream) { - transport_->OpenStream(stream); - peer_->OpenStream(stream); - streams_.push_back(stream); - } - - UsrsctpTransport* transport_; - UsrsctpTransport* peer_; - std::vector<int> streams_; -}; - -// SCTP Data Engine testing framework. -class SctpTransportTest : public ::testing::Test, public sigslot::has_slots<> { - protected: - // usrsctp uses the NSS random number generator on non-Android platforms, - // so we need to initialize SSL. - static void SetUpTestSuite() {} - - void SetupConnectedTransportsWithTwoStreams() { - SetupConnectedTransportsWithTwoStreams(kTransport1Port, kTransport2Port); - } - - void SetupConnectedTransportsWithTwoStreams(int port1, int port2) { - fake_dtls1_.reset(new FakeDtlsTransport("fake dtls 1", 0)); - fake_dtls2_.reset(new FakeDtlsTransport("fake dtls 2", 0)); - recv1_.reset(new SctpFakeDataReceiver()); - recv2_.reset(new SctpFakeDataReceiver()); - transport1_.reset(CreateTransport(fake_dtls1_.get(), recv1_.get())); - transport1_->set_debug_name_for_testing("transport1"); - transport1_->SignalReadyToSendData.connect( - this, &SctpTransportTest::OnChan1ReadyToSend); - transport2_.reset(CreateTransport(fake_dtls2_.get(), recv2_.get())); - transport2_->set_debug_name_for_testing("transport2"); - transport2_->SignalReadyToSendData.connect( - this, &SctpTransportTest::OnChan2ReadyToSend); - // Setup two connected transports ready to send and receive. - bool asymmetric = false; - fake_dtls1_->SetDestination(fake_dtls2_.get(), asymmetric); - - RTC_LOG(LS_VERBOSE) << "Transport setup ----------------------------- "; - AddStream(1); - AddStream(2); - - RTC_LOG(LS_VERBOSE) - << "Connect the transports -----------------------------"; - // Both transports need to have started (with matching ports) for an - // association to be formed. - transport1_->Start(port1, port2, kSctpSendBufferSize); - transport2_->Start(port2, port1, kSctpSendBufferSize); - } - - bool AddStream(int sid) { - bool ret = true; - ret = ret && transport1_->OpenStream(sid); - ret = ret && transport2_->OpenStream(sid); - return ret; - } - - UsrsctpTransport* CreateTransport(FakeDtlsTransport* fake_dtls, - SctpFakeDataReceiver* recv) { - UsrsctpTransport* transport = - new UsrsctpTransport(rtc::Thread::Current(), fake_dtls); - // When data is received, pass it to the SctpFakeDataReceiver. - transport->SignalDataReceived.connect( - recv, &SctpFakeDataReceiver::OnDataReceived); - return transport; - } - - bool SendData(UsrsctpTransport* chan, - int sid, - const std::string& msg, - SendDataResult* result, - bool ordered = false) { - webrtc::SendDataParams params; - params.ordered = ordered; - - return chan->SendData( - sid, params, rtc::CopyOnWriteBuffer(&msg[0], msg.length()), result); - } - - bool ReceivedData(const SctpFakeDataReceiver* recv, - int sid, - const std::string& msg) { - return (recv->received() && recv->last_params().sid == sid && - recv->last_data() == msg); - } - - bool ProcessMessagesUntilIdle() { - rtc::Thread* thread = rtc::Thread::Current(); - while (!thread->empty()) { - rtc::Message msg; - if (thread->Get(&msg, rtc::Thread::kForever)) { - thread->Dispatch(&msg); - } - } - return !thread->IsQuitting(); - } - - UsrsctpTransport* transport1() { return transport1_.get(); } - UsrsctpTransport* transport2() { return transport2_.get(); } - SctpFakeDataReceiver* receiver1() { return recv1_.get(); } - SctpFakeDataReceiver* receiver2() { return recv2_.get(); } - FakeDtlsTransport* fake_dtls1() { return fake_dtls1_.get(); } - FakeDtlsTransport* fake_dtls2() { return fake_dtls2_.get(); } - - int transport1_ready_to_send_count() { - return transport1_ready_to_send_count_; - } - int transport2_ready_to_send_count() { - return transport2_ready_to_send_count_; - } - - private: - std::unique_ptr<FakeDtlsTransport> fake_dtls1_; - std::unique_ptr<FakeDtlsTransport> fake_dtls2_; - std::unique_ptr<SctpFakeDataReceiver> recv1_; - std::unique_ptr<SctpFakeDataReceiver> recv2_; - std::unique_ptr<UsrsctpTransport> transport1_; - std::unique_ptr<UsrsctpTransport> transport2_; - - int transport1_ready_to_send_count_ = 0; - int transport2_ready_to_send_count_ = 0; - - void OnChan1ReadyToSend() { ++transport1_ready_to_send_count_; } - void OnChan2ReadyToSend() { ++transport2_ready_to_send_count_; } -}; - -TEST_F(SctpTransportTest, MessageInterleavedWithNotification) { - FakeDtlsTransport fake_dtls1("fake dtls 1", 0); - FakeDtlsTransport fake_dtls2("fake dtls 2", 0); - SctpFakeDataReceiver recv1; - SctpFakeDataReceiver recv2; - std::unique_ptr<UsrsctpTransport> transport1( - CreateTransport(&fake_dtls1, &recv1)); - std::unique_ptr<UsrsctpTransport> transport2( - CreateTransport(&fake_dtls2, &recv2)); - - // Add a stream. - transport1->OpenStream(1); - transport2->OpenStream(1); - - // Start SCTP transports. - transport1->Start(kSctpDefaultPort, kSctpDefaultPort, kSctpSendBufferSize); - transport2->Start(kSctpDefaultPort, kSctpDefaultPort, kSctpSendBufferSize); - - // Connect the two fake DTLS transports. - fake_dtls1.SetDestination(&fake_dtls2, false); - - // Ensure the SCTP association has been established - // Note: I'd rather watch for an assoc established state here but couldn't - // find any exposed... - SendDataResult result; - ASSERT_TRUE(SendData(transport2.get(), 1, "meow", &result)); - EXPECT_TRUE_WAIT(ReceivedData(&recv1, 1, "meow"), kDefaultTimeout); - - // Detach the DTLS transport to ensure only we will inject packets from here - // on. - transport1->SetDtlsTransport(nullptr); - - // Prepare chunk buffer and metadata - auto chunk = rtc::CopyOnWriteBuffer(32); - struct sctp_rcvinfo meta = {0}; - meta.rcv_sid = 1; - meta.rcv_ssn = 1337; - meta.rcv_ppid = rtc::HostToNetwork32(51); // text (complete) - - // Inject chunk 1/2. - meta.rcv_tsn = 42; - meta.rcv_cumtsn = 42; - chunk.SetData("meow?", 5); - transport1->InjectDataOrNotificationFromSctpForTesting(chunk.data(), - chunk.size(), meta, 0); - - // Inject a notification in between chunks. - union sctp_notification notification; - memset(¬ification, 0, sizeof(notification)); - // Type chosen since it's not handled apart from being logged - notification.sn_header.sn_type = SCTP_PEER_ADDR_CHANGE; - notification.sn_header.sn_flags = 0; - notification.sn_header.sn_length = sizeof(notification); - transport1->InjectDataOrNotificationFromSctpForTesting( - ¬ification, sizeof(notification), {0}, MSG_NOTIFICATION); - - // Inject chunk 2/2 - meta.rcv_tsn = 42; - meta.rcv_cumtsn = 43; - chunk.SetData(" rawr!", 6); - transport1->InjectDataOrNotificationFromSctpForTesting( - chunk.data(), chunk.size(), meta, MSG_EOR); - - // Expect the message to contain both chunks. - EXPECT_TRUE_WAIT(ReceivedData(&recv1, 1, "meow? rawr!"), kDefaultTimeout); -} - -// Test that data can be sent end-to-end when an SCTP transport starts with one -// transport (which is unwritable), and then switches to another transport. A -// common scenario due to how BUNDLE works. -TEST_F(SctpTransportTest, SwitchDtlsTransport) { - FakeDtlsTransport black_hole("black hole", 0); - FakeDtlsTransport fake_dtls1("fake dtls 1", 0); - FakeDtlsTransport fake_dtls2("fake dtls 2", 0); - SctpFakeDataReceiver recv1; - SctpFakeDataReceiver recv2; - - // Construct transport1 with the "black hole" transport. - std::unique_ptr<UsrsctpTransport> transport1( - CreateTransport(&black_hole, &recv1)); - std::unique_ptr<UsrsctpTransport> transport2( - CreateTransport(&fake_dtls2, &recv2)); - - // Add a stream. - transport1->OpenStream(1); - transport2->OpenStream(1); - - // Tell them both to start (though transport1_ is connected to black_hole). - transport1->Start(kTransport1Port, kTransport2Port, kSctpSendBufferSize); - transport2->Start(kTransport2Port, kTransport1Port, kSctpSendBufferSize); - - // Switch transport1_ to the normal fake_dtls1_ transport. - transport1->SetDtlsTransport(&fake_dtls1); - - // Connect the two fake DTLS transports. - bool asymmetric = false; - fake_dtls1.SetDestination(&fake_dtls2, asymmetric); - - // Make sure we end up able to send data. - SendDataResult result; - ASSERT_TRUE(SendData(transport1.get(), 1, "foo", &result)); - ASSERT_TRUE(SendData(transport2.get(), 1, "bar", &result)); - EXPECT_TRUE_WAIT(ReceivedData(&recv2, 1, "foo"), kDefaultTimeout); - EXPECT_TRUE_WAIT(ReceivedData(&recv1, 1, "bar"), kDefaultTimeout); - - // Setting a null DtlsTransport should work. This could happen when an SCTP - // data section is rejected. - transport1->SetDtlsTransport(nullptr); -} - -// Calling Start twice shouldn't do anything bad, if with the same parameters. -TEST_F(SctpTransportTest, DuplicateStartCallsIgnored) { - SetupConnectedTransportsWithTwoStreams(); - EXPECT_TRUE(transport1()->Start(kTransport1Port, kTransport2Port, - kSctpSendBufferSize)); - - // Make sure we can still send/recv data. - SendDataResult result; - ASSERT_TRUE(SendData(transport1(), 1, "foo", &result)); - ASSERT_TRUE(SendData(transport2(), 1, "bar", &result)); - EXPECT_TRUE_WAIT(ReceivedData(receiver2(), 1, "foo"), kDefaultTimeout); - EXPECT_TRUE_WAIT(ReceivedData(receiver1(), 1, "bar"), kDefaultTimeout); -} - -// Calling Start a second time with a different port should fail. -TEST_F(SctpTransportTest, CallingStartWithDifferentPortFails) { - SetupConnectedTransportsWithTwoStreams(); - EXPECT_FALSE(transport1()->Start(kTransport1Port, 1234, kSctpSendBufferSize)); - EXPECT_FALSE(transport1()->Start(1234, kTransport2Port, kSctpSendBufferSize)); -} - -// A value of -1 for the local/remote port should be treated as the default -// (5000). -TEST_F(SctpTransportTest, NegativeOnePortTreatedAsDefault) { - FakeDtlsTransport fake_dtls1("fake dtls 1", 0); - FakeDtlsTransport fake_dtls2("fake dtls 2", 0); - SctpFakeDataReceiver recv1; - SctpFakeDataReceiver recv2; - std::unique_ptr<UsrsctpTransport> transport1( - CreateTransport(&fake_dtls1, &recv1)); - std::unique_ptr<UsrsctpTransport> transport2( - CreateTransport(&fake_dtls2, &recv2)); - - // Add a stream. - transport1->OpenStream(1); - transport2->OpenStream(1); - - // Tell them both to start, giving one transport the default port and the - // other transport -1. - transport1->Start(kSctpDefaultPort, kSctpDefaultPort, kSctpSendBufferSize); - transport2->Start(-1, -1, kSctpSendBufferSize); - - // Connect the two fake DTLS transports. - bool asymmetric = false; - fake_dtls1.SetDestination(&fake_dtls2, asymmetric); - - // Make sure we end up able to send data. - SendDataResult result; - ASSERT_TRUE(SendData(transport1.get(), 1, "foo", &result)); - ASSERT_TRUE(SendData(transport2.get(), 1, "bar", &result)); - EXPECT_TRUE_WAIT(ReceivedData(&recv2, 1, "foo"), kDefaultTimeout); - EXPECT_TRUE_WAIT(ReceivedData(&recv1, 1, "bar"), kDefaultTimeout); -} - -TEST_F(SctpTransportTest, OpenStreamWithAlreadyOpenedStreamFails) { - FakeDtlsTransport fake_dtls("fake dtls", 0); - SctpFakeDataReceiver recv; - std::unique_ptr<UsrsctpTransport> transport( - CreateTransport(&fake_dtls, &recv)); - EXPECT_TRUE(transport->OpenStream(1)); - EXPECT_FALSE(transport->OpenStream(1)); -} - -TEST_F(SctpTransportTest, ResetStreamWithAlreadyResetStreamFails) { - FakeDtlsTransport fake_dtls("fake dtls", 0); - SctpFakeDataReceiver recv; - std::unique_ptr<UsrsctpTransport> transport( - CreateTransport(&fake_dtls, &recv)); - EXPECT_TRUE(transport->OpenStream(1)); - EXPECT_TRUE(transport->ResetStream(1)); - EXPECT_FALSE(transport->ResetStream(1)); -} - -// Test that SignalReadyToSendData is fired after Start has been called and the -// DTLS transport is writable. -TEST_F(SctpTransportTest, SignalReadyToSendDataAfterDtlsWritable) { - FakeDtlsTransport fake_dtls("fake dtls", 0); - SctpFakeDataReceiver recv; - std::unique_ptr<UsrsctpTransport> transport( - CreateTransport(&fake_dtls, &recv)); - SctpTransportObserver observer(transport.get()); - - transport->Start(kSctpDefaultPort, kSctpDefaultPort, kSctpSendBufferSize); - fake_dtls.SetWritable(true); - EXPECT_TRUE_WAIT(observer.ReadyToSend(), kDefaultTimeout); -} - -// Run the below tests using both ordered and unordered mode. -class SctpTransportTestWithOrdered - : public SctpTransportTest, - public ::testing::WithParamInterface<bool> {}; - -// Tests that a small message gets buffered and later sent by the -// UsrsctpTransport when the sctp library only accepts the message partially. -TEST_P(SctpTransportTestWithOrdered, SendSmallBufferedOutgoingMessage) { - bool ordered = GetParam(); - SetupConnectedTransportsWithTwoStreams(); - // Wait for initial SCTP association to be formed. - EXPECT_EQ_WAIT(1, transport1_ready_to_send_count(), kDefaultTimeout); - // Make the fake transport unwritable so that messages pile up for the SCTP - // socket. - fake_dtls1()->SetWritable(false); - SendDataResult result; - - // Fill almost all of sctp library's send buffer. - ASSERT_TRUE(SendData(transport1(), /*sid=*/1, - std::string(kSctpSendBufferSize - 1, 'a'), &result, - ordered)); - - std::string buffered_message("hello hello"); - // UsrsctpTransport accepts this message by buffering part of it. - ASSERT_TRUE( - SendData(transport1(), /*sid=*/1, buffered_message, &result, ordered)); - ASSERT_TRUE(transport1()->ReadyToSendData()); - - // Sending anything else should block now. - ASSERT_FALSE( - SendData(transport1(), /*sid=*/1, "hello again", &result, ordered)); - ASSERT_EQ(SDR_BLOCK, result); - ASSERT_FALSE(transport1()->ReadyToSendData()); - - // Make sure the ready-to-send count hasn't changed. - EXPECT_EQ(1, transport1_ready_to_send_count()); - // Make the transport writable again and expect a "SignalReadyToSendData" at - // some point after sending the buffered message. - fake_dtls1()->SetWritable(true); - EXPECT_EQ_WAIT(2, transport1_ready_to_send_count(), kDefaultTimeout); - EXPECT_TRUE_WAIT(ReceivedData(receiver2(), 1, buffered_message), - kDefaultTimeout); - EXPECT_EQ(2u, receiver2()->num_messages_received()); -} - -// Tests that a large message gets buffered and later sent by the -// UsrsctpTransport when the sctp library only accepts the message partially. -TEST_P(SctpTransportTestWithOrdered, SendLargeBufferedOutgoingMessage) { - bool ordered = GetParam(); - SetupConnectedTransportsWithTwoStreams(); - // Wait for initial SCTP association to be formed. - EXPECT_EQ_WAIT(1, transport1_ready_to_send_count(), kDefaultTimeout); - // Make the fake transport unwritable so that messages pile up for the SCTP - // socket. - fake_dtls1()->SetWritable(false); - SendDataResult result; - - // Fill almost all of sctp library's send buffer. - ASSERT_TRUE(SendData(transport1(), /*sid=*/1, - std::string(kSctpSendBufferSize / 2, 'a'), &result, - ordered)); - - std::string buffered_message(kSctpSendBufferSize, 'b'); - // UsrsctpTransport accepts this message by buffering the second half. - ASSERT_TRUE( - SendData(transport1(), /*sid=*/1, buffered_message, &result, ordered)); - ASSERT_TRUE(transport1()->ReadyToSendData()); - - // Sending anything else should block now. - ASSERT_FALSE( - SendData(transport1(), /*sid=*/1, "hello again", &result, ordered)); - ASSERT_EQ(SDR_BLOCK, result); - ASSERT_FALSE(transport1()->ReadyToSendData()); - - // Make sure the ready-to-send count hasn't changed. - EXPECT_EQ(1, transport1_ready_to_send_count()); - // Make the transport writable again and expect a "SignalReadyToSendData" at - // some point. - fake_dtls1()->SetWritable(true); - EXPECT_EQ_WAIT(2, transport1_ready_to_send_count(), kDefaultTimeout); - EXPECT_TRUE_WAIT(ReceivedData(receiver2(), 1, buffered_message), - kDefaultTimeout); - EXPECT_EQ(2u, receiver2()->num_messages_received()); -} - -// Tests that a large message gets buffered and later sent by the -// UsrsctpTransport when the sctp library only accepts the message partially -// during a stream reset. -TEST_P(SctpTransportTestWithOrdered, - SendLargeBufferedOutgoingMessageDuringReset) { - bool ordered = GetParam(); - SetupConnectedTransportsWithTwoStreams(); - SctpTransportObserver transport2_observer(transport2()); - - // Wait for initial SCTP association to be formed. - EXPECT_EQ_WAIT(1, transport1_ready_to_send_count(), kDefaultTimeout); - // Make the fake transport unwritable so that messages pile up for the SCTP - // socket. - fake_dtls1()->SetWritable(false); - SendDataResult result; - - // Fill almost all of sctp library's send buffer. - ASSERT_TRUE(SendData(transport1(), /*sid=*/1, - std::string(kSctpSendBufferSize / 2, 'a'), &result, - ordered)); - - std::string buffered_message(kSctpSendBufferSize, 'b'); - // UsrsctpTransport accepts this message by buffering the second half. - ASSERT_TRUE( - SendData(transport1(), /*sid=*/1, buffered_message, &result, ordered)); - // Queue a stream reset - transport1()->ResetStream(/*sid=*/1); - - // Make the transport writable again and expect a "SignalReadyToSendData" at - // some point after sending the buffered message. - fake_dtls1()->SetWritable(true); - EXPECT_EQ_WAIT(2, transport1_ready_to_send_count(), kDefaultTimeout); - - // Queued message should be received by the receiver before receiving the - // reset - EXPECT_TRUE_WAIT(ReceivedData(receiver2(), 1, buffered_message), - kDefaultTimeout); - EXPECT_EQ(2u, receiver2()->num_messages_received()); - EXPECT_TRUE_WAIT(transport2_observer.WasStreamClosed(1), kDefaultTimeout); -} - -TEST_P(SctpTransportTestWithOrdered, SendData) { - bool ordered = GetParam(); - SetupConnectedTransportsWithTwoStreams(); - - SendDataResult result; - RTC_LOG(LS_VERBOSE) - << "transport1 sending: 'hello?' -----------------------------"; - ASSERT_TRUE(SendData(transport1(), 1, "hello?", &result, ordered)); - EXPECT_EQ(SDR_SUCCESS, result); - EXPECT_TRUE_WAIT(ReceivedData(receiver2(), 1, "hello?"), kDefaultTimeout); - RTC_LOG(LS_VERBOSE) << "recv2.received=" << receiver2()->received() - << ", recv2.last_params.sid=" - << receiver2()->last_params().sid - << ", recv2.last_params.seq_num=" - << receiver2()->last_params().seq_num - << ", recv2.last_data=" << receiver2()->last_data(); - - RTC_LOG(LS_VERBOSE) - << "transport2 sending: 'hi transport1' -----------------------------"; - ASSERT_TRUE(SendData(transport2(), 2, "hi transport1", &result, ordered)); - EXPECT_EQ(SDR_SUCCESS, result); - EXPECT_TRUE_WAIT(ReceivedData(receiver1(), 2, "hi transport1"), - kDefaultTimeout); - RTC_LOG(LS_VERBOSE) << "recv1.received=" << receiver1()->received() - << ", recv1.last_params.sid=" - << receiver1()->last_params().sid - << ", recv1.last_params.seq_num=" - << receiver1()->last_params().seq_num - << ", recv1.last_data=" << receiver1()->last_data(); -} - -// Sends a lot of large messages at once and verifies SDR_BLOCK is returned. -TEST_P(SctpTransportTestWithOrdered, SendDataBlocked) { - SetupConnectedTransportsWithTwoStreams(); - - SendDataResult result; - webrtc::SendDataParams params; - params.ordered = GetParam(); - - std::vector<char> buffer(1024 * 64, 0); - - for (size_t i = 0; i < 100; ++i) { - transport1()->SendData( - 1, params, rtc::CopyOnWriteBuffer(&buffer[0], buffer.size()), &result); - if (result == SDR_BLOCK) - break; - } - - EXPECT_EQ(SDR_BLOCK, result); -} - -// Test that after an SCTP socket's buffer is filled, SignalReadyToSendData -// is fired after it begins to be drained. -TEST_P(SctpTransportTestWithOrdered, SignalReadyToSendDataAfterBlocked) { - SetupConnectedTransportsWithTwoStreams(); - // Wait for initial SCTP association to be formed. - EXPECT_EQ_WAIT(1, transport1_ready_to_send_count(), kDefaultTimeout); - // Make the fake transport unwritable so that messages pile up for the SCTP - // socket. - fake_dtls1()->SetWritable(false); - // Send messages until we get EWOULDBLOCK. - static const size_t kMaxMessages = 1024; - webrtc::SendDataParams params; - params.ordered = GetParam(); - rtc::CopyOnWriteBuffer buf(1024); - memset(buf.MutableData(), 0, 1024); - SendDataResult result; - size_t message_count = 0; - for (; message_count < kMaxMessages; ++message_count) { - if (!transport1()->SendData(1, params, buf, &result) && - result == SDR_BLOCK) { - break; - } - } - ASSERT_NE(kMaxMessages, message_count) - << "Sent max number of messages without getting SDR_BLOCK?"; - // Make sure the ready-to-send count hasn't changed. - EXPECT_EQ(1, transport1_ready_to_send_count()); - // Make the transport writable again and expect a "SignalReadyToSendData" at - // some point. - fake_dtls1()->SetWritable(true); - EXPECT_EQ_WAIT(2, transport1_ready_to_send_count(), kDefaultTimeout); - EXPECT_EQ_WAIT(message_count, receiver2()->num_messages_received(), - kDefaultTimeout); -} - -INSTANTIATE_TEST_SUITE_P(SctpTransportTest, - SctpTransportTestWithOrdered, - ::testing::Bool()); - -// This is a regression test that fails with earlier versions of SCTP in -// unordered mode. See bugs.webrtc.org/10939. -TEST_F(SctpTransportTest, SendsLargeDataBufferedBySctpLib) { - SetupConnectedTransportsWithTwoStreams(); - // Wait for initial SCTP association to be formed. - EXPECT_EQ_WAIT(1, transport1_ready_to_send_count(), kDefaultTimeout); - // Make the fake transport unwritable so that messages pile up for the SCTP - // socket. - fake_dtls1()->SetWritable(false); - - SendDataResult result; - std::string buffered_message(kSctpSendBufferSize - 1, 'a'); - ASSERT_TRUE(SendData(transport1(), 1, buffered_message, &result, false)); - - fake_dtls1()->SetWritable(true); - EXPECT_EQ_WAIT(1, transport1_ready_to_send_count(), kDefaultTimeout); - EXPECT_TRUE_WAIT(ReceivedData(receiver2(), 1, buffered_message), - kDefaultTimeout); -} - -// Trying to send data for a nonexistent stream should fail. -TEST_F(SctpTransportTest, SendDataWithNonexistentStreamFails) { - SetupConnectedTransportsWithTwoStreams(); - SendDataResult result; - EXPECT_FALSE(SendData(transport2(), 123, "some data", &result)); - EXPECT_EQ(SDR_ERROR, result); -} - -TEST_F(SctpTransportTest, SendDataHighPorts) { - SetupConnectedTransportsWithTwoStreams(32768, 32769); - - SendDataResult result; - ASSERT_TRUE(SendData(transport1(), 1, "hello?", &result)); - EXPECT_EQ(SDR_SUCCESS, result); - EXPECT_TRUE_WAIT(ReceivedData(receiver2(), 1, "hello?"), kDefaultTimeout); - - ASSERT_TRUE(SendData(transport2(), 2, "hi transport1", &result)); - EXPECT_EQ(SDR_SUCCESS, result); - EXPECT_TRUE_WAIT(ReceivedData(receiver1(), 2, "hi transport1"), - kDefaultTimeout); -} - -TEST_F(SctpTransportTest, ClosesRemoteStream) { - SetupConnectedTransportsWithTwoStreams(); - SctpTransportObserver transport1_observer(transport1()); - SctpTransportObserver transport2_observer(transport2()); - - SendDataResult result; - ASSERT_TRUE(SendData(transport1(), 1, "hello?", &result)); - EXPECT_EQ(SDR_SUCCESS, result); - EXPECT_TRUE_WAIT(ReceivedData(receiver2(), 1, "hello?"), kDefaultTimeout); - ASSERT_TRUE(SendData(transport2(), 2, "hi transport1", &result)); - EXPECT_EQ(SDR_SUCCESS, result); - EXPECT_TRUE_WAIT(ReceivedData(receiver1(), 2, "hi transport1"), - kDefaultTimeout); - - // Close stream 1 on transport 1. Transport 2 should notify us. - transport1()->ResetStream(1); - EXPECT_TRUE_WAIT(transport2_observer.WasStreamClosed(1), kDefaultTimeout); -} -TEST_F(SctpTransportTest, ClosesRemoteStreamWithNoData) { - SetupConnectedTransportsWithTwoStreams(); - SctpTransportObserver transport1_observer(transport1()); - SctpTransportObserver transport2_observer(transport2()); - - // Close stream 1 on transport 1. Transport 2 should notify us. - transport1()->ResetStream(1); - EXPECT_TRUE_WAIT(transport2_observer.WasStreamClosed(1), kDefaultTimeout); -} - -TEST_F(SctpTransportTest, ClosesTwoRemoteStreams) { - SetupConnectedTransportsWithTwoStreams(); - AddStream(3); - SctpTransportObserver transport1_observer(transport1()); - SctpTransportObserver transport2_observer(transport2()); - - SendDataResult result; - ASSERT_TRUE(SendData(transport1(), 1, "hello?", &result)); - EXPECT_EQ(SDR_SUCCESS, result); - EXPECT_TRUE_WAIT(ReceivedData(receiver2(), 1, "hello?"), kDefaultTimeout); - ASSERT_TRUE(SendData(transport2(), 2, "hi transport1", &result)); - EXPECT_EQ(SDR_SUCCESS, result); - EXPECT_TRUE_WAIT(ReceivedData(receiver1(), 2, "hi transport1"), - kDefaultTimeout); - - // Close two streams on one side. - transport2()->ResetStream(2); - transport2()->ResetStream(3); - EXPECT_TRUE_WAIT(transport2_observer.WasStreamClosed(2), kDefaultTimeout); - EXPECT_TRUE_WAIT(transport2_observer.WasStreamClosed(3), kDefaultTimeout); -} - -TEST_F(SctpTransportTest, ClosesStreamsOnBothSides) { - SetupConnectedTransportsWithTwoStreams(); - AddStream(3); - AddStream(4); - SctpTransportObserver transport1_observer(transport1()); - SctpTransportObserver transport2_observer(transport2()); - - SendDataResult result; - ASSERT_TRUE(SendData(transport1(), 1, "hello?", &result)); - EXPECT_EQ(SDR_SUCCESS, result); - EXPECT_TRUE_WAIT(ReceivedData(receiver2(), 1, "hello?"), kDefaultTimeout); - ASSERT_TRUE(SendData(transport2(), 2, "hi transport1", &result)); - EXPECT_EQ(SDR_SUCCESS, result); - EXPECT_TRUE_WAIT(ReceivedData(receiver1(), 2, "hi transport1"), - kDefaultTimeout); - - // Close one stream on transport1(), while closing three streams on - // transport2(). They will conflict (only one side can close anything at a - // time, apparently). Test the resolution of the conflict. - transport1()->ResetStream(1); - - transport2()->ResetStream(2); - transport2()->ResetStream(3); - transport2()->ResetStream(4); - EXPECT_TRUE_WAIT(transport2_observer.WasStreamClosed(1), kDefaultTimeout); - EXPECT_TRUE_WAIT(transport1_observer.WasStreamClosed(2), kDefaultTimeout); - EXPECT_TRUE_WAIT(transport1_observer.WasStreamClosed(3), kDefaultTimeout); - EXPECT_TRUE_WAIT(transport1_observer.WasStreamClosed(4), kDefaultTimeout); -} - -TEST_F(SctpTransportTest, RefusesHighNumberedTransports) { - SetupConnectedTransportsWithTwoStreams(); - EXPECT_TRUE(AddStream(kMaxSctpSid)); - EXPECT_FALSE(AddStream(kMaxSctpSid + 1)); -} - -TEST_F(SctpTransportTest, ReusesAStream) { - // Shut down transport 1, then open it up again for reuse. - SetupConnectedTransportsWithTwoStreams(); - SendDataResult result; - SctpTransportObserver transport2_observer(transport2()); - - ASSERT_TRUE(SendData(transport1(), 1, "hello?", &result)); - EXPECT_EQ(SDR_SUCCESS, result); - EXPECT_TRUE_WAIT(ReceivedData(receiver2(), 1, "hello?"), kDefaultTimeout); - - transport1()->ResetStream(1); - EXPECT_TRUE_WAIT(transport2_observer.WasStreamClosed(1), kDefaultTimeout); - // Transport 1 is gone now. - - // Create a new transport 1. - AddStream(1); - ASSERT_TRUE(SendData(transport1(), 1, "hi?", &result)); - EXPECT_EQ(SDR_SUCCESS, result); - EXPECT_TRUE_WAIT(ReceivedData(receiver2(), 1, "hi?"), kDefaultTimeout); - transport1()->ResetStream(1); - EXPECT_EQ_WAIT(2, transport2_observer.StreamCloseCount(1), kDefaultTimeout); -} - -TEST_F(SctpTransportTest, RejectsTooLargeMessageSize) { - FakeDtlsTransport fake_dtls("fake dtls", 0); - SctpFakeDataReceiver recv; - std::unique_ptr<UsrsctpTransport> transport( - CreateTransport(&fake_dtls, &recv)); - - EXPECT_FALSE(transport->Start(kSctpDefaultPort, kSctpDefaultPort, - kSctpSendBufferSize + 1)); -} - -TEST_F(SctpTransportTest, RejectsTooSmallMessageSize) { - FakeDtlsTransport fake_dtls("fake dtls", 0); - SctpFakeDataReceiver recv; - std::unique_ptr<UsrsctpTransport> transport( - CreateTransport(&fake_dtls, &recv)); - - EXPECT_FALSE(transport->Start(kSctpDefaultPort, kSctpDefaultPort, 0)); -} - -TEST_F(SctpTransportTest, RejectsSendTooLargeMessages) { - SetupConnectedTransportsWithTwoStreams(); - // Use "Start" to reduce the max message size - transport1()->Start(kTransport1Port, kTransport2Port, 10); - EXPECT_EQ(10, transport1()->max_message_size()); - const char eleven_characters[] = "12345678901"; - SendDataResult result; - EXPECT_FALSE(SendData(transport1(), 1, eleven_characters, &result)); -} - -// Regression test for: crbug.com/1137936 -TEST_F(SctpTransportTest, SctpRestartWithPendingDataDoesNotDeadlock) { - // In order to trigger a restart, we'll connect two transports, then - // disconnect them and connect the first to a third, which will initiate the - // new handshake. - FakeDtlsTransport fake_dtls1("fake dtls 1", 0); - FakeDtlsTransport fake_dtls2("fake dtls 2", 0); - FakeDtlsTransport fake_dtls3("fake dtls 3", 0); - SctpFakeDataReceiver recv1; - SctpFakeDataReceiver recv2; - SctpFakeDataReceiver recv3; - - std::unique_ptr<UsrsctpTransport> transport1( - CreateTransport(&fake_dtls1, &recv1)); - std::unique_ptr<UsrsctpTransport> transport2( - CreateTransport(&fake_dtls2, &recv2)); - std::unique_ptr<UsrsctpTransport> transport3( - CreateTransport(&fake_dtls3, &recv3)); - SctpTransportObserver observer(transport1.get()); - - // Connect the first two transports. - fake_dtls1.SetDestination(&fake_dtls2, /*asymmetric=*/false); - transport1->OpenStream(1); - transport2->OpenStream(1); - transport1->Start(5000, 5000, kSctpSendBufferSize); - transport2->Start(5000, 5000, kSctpSendBufferSize); - - // Sanity check that we can send data. - SendDataResult result; - ASSERT_TRUE(SendData(transport1.get(), 1, "foo", &result)); - ASSERT_TRUE_WAIT(ReceivedData(&recv2, 1, "foo"), kDefaultTimeout); - - // Disconnect the transports and attempt to send a message, which will be - // stored in an output queue; this is necessary to reproduce the bug. - fake_dtls1.SetDestination(nullptr, /*asymmetric=*/false); - EXPECT_TRUE(SendData(transport1.get(), 1, "bar", &result)); - - // Now connect to the third transport. - fake_dtls1.SetDestination(&fake_dtls3, /*asymmetric=*/false); - transport3->OpenStream(1); - transport3->Start(5000, 5000, kSctpSendBufferSize); - - // Send data from the new endpoint to the original endpoint. If data is - // received that means the restart must have been successful. - EXPECT_TRUE(SendData(transport3.get(), 1, "baz", &result)); - EXPECT_TRUE_WAIT(ReceivedData(&recv1, 1, "baz"), kDefaultTimeout); -} - -} // namespace cricket |