1 /*
2 * Copyright (c) 2019 The WebRTC project authors. All Rights Reserved.
3 *
4 * Use of this source code is governed by a BSD-style license
5 * that can be found in the LICENSE file in the root of the source
6 * tree. An additional intellectual property rights grant can be found
7 * in the file PATENTS. All contributing project authors may
8 * be found in the AUTHORS file in the root of the source tree.
9 */
10 #include "media/sctp/sctp_transport.h"
11
12 #include <memory>
13 #include <queue>
14 #include <string>
15
16 #include "media/sctp/sctp_transport_internal.h"
17 #include "rtc_base/copy_on_write_buffer.h"
18 #include "rtc_base/gunit.h"
19 #include "rtc_base/logging.h"
20 #include "rtc_base/random.h"
21 #include "rtc_base/synchronization/mutex.h"
22 #include "rtc_base/thread.h"
23 #include "test/gtest.h"
24
25 namespace {
26
27 static constexpr int kDefaultTimeout = 10000; // 10 seconds.
28 static constexpr int kTransport1Port = 15001;
29 static constexpr int kTransport2Port = 25002;
30 static constexpr int kLogPerMessagesCount = 100;
31
32 /**
33 * An simple packet transport implementation which can be
34 * configured to simulate uniform random packet loss and
35 * configurable random packet delay and reordering.
36 */
37 class SimulatedPacketTransport final : public rtc::PacketTransportInternal {
38 public:
SimulatedPacketTransport(std::string name,rtc::Thread * transport_thread,uint8_t packet_loss_percents,uint16_t avg_send_delay_millis)39 SimulatedPacketTransport(std::string name,
40 rtc::Thread* transport_thread,
41 uint8_t packet_loss_percents,
42 uint16_t avg_send_delay_millis)
43 : transport_name_(name),
44 transport_thread_(transport_thread),
45 packet_loss_percents_(packet_loss_percents),
46 avg_send_delay_millis_(avg_send_delay_millis),
47 random_(42) {
48 RTC_DCHECK(transport_thread_);
49 RTC_DCHECK_LE(packet_loss_percents_, 100);
50 RTC_DCHECK_RUN_ON(transport_thread_);
51 }
52
~SimulatedPacketTransport()53 ~SimulatedPacketTransport() override {
54 RTC_DCHECK_RUN_ON(transport_thread_);
55 auto destination = destination_.load();
56 if (destination != nullptr) {
57 invoker_.Flush(destination->transport_thread_);
58 }
59 invoker_.Flush(transport_thread_);
60 destination_ = nullptr;
61 SignalWritableState(this);
62 }
63
transport_name() const64 const std::string& transport_name() const override { return transport_name_; }
65
writable() const66 bool writable() const override { return destination_ != nullptr; }
67
receiving() const68 bool receiving() const override { return true; }
69
SendPacket(const char * data,size_t len,const rtc::PacketOptions & options,int flags=0)70 int SendPacket(const char* data,
71 size_t len,
72 const rtc::PacketOptions& options,
73 int flags = 0) {
74 RTC_DCHECK_RUN_ON(transport_thread_);
75 auto destination = destination_.load();
76 if (destination == nullptr) {
77 return -1;
78 }
79 if (random_.Rand(100) < packet_loss_percents_) {
80 // silent packet loss
81 return 0;
82 }
83 rtc::CopyOnWriteBuffer buffer(data, len);
84 auto send_job = [this, flags, buffer = std::move(buffer)] {
85 auto destination = destination_.load();
86 if (destination == nullptr) {
87 return;
88 }
89 destination->SignalReadPacket(
90 destination, reinterpret_cast<const char*>(buffer.data()),
91 buffer.size(), rtc::Time(), flags);
92 };
93 // Introduce random send delay in range [0 .. 2 * avg_send_delay_millis_]
94 // millis, which will also work as random packet reordering mechanism.
95 uint16_t actual_send_delay = avg_send_delay_millis_;
96 int16_t reorder_delay =
97 avg_send_delay_millis_ *
98 std::min(1.0, std::max(-1.0, random_.Gaussian(0, 0.5)));
99 actual_send_delay += reorder_delay;
100
101 if (actual_send_delay > 0) {
102 invoker_.AsyncInvokeDelayed<void>(RTC_FROM_HERE,
103 destination->transport_thread_,
104 std::move(send_job), actual_send_delay);
105 } else {
106 invoker_.AsyncInvoke<void>(RTC_FROM_HERE, destination->transport_thread_,
107 std::move(send_job));
108 }
109 return 0;
110 }
111
SetOption(rtc::Socket::Option opt,int value)112 int SetOption(rtc::Socket::Option opt, int value) override { return 0; }
113
GetOption(rtc::Socket::Option opt,int * value)114 bool GetOption(rtc::Socket::Option opt, int* value) override { return false; }
115
GetError()116 int GetError() override { return 0; }
117
network_route() const118 absl::optional<rtc::NetworkRoute> network_route() const override {
119 return absl::nullopt;
120 }
121
SetDestination(SimulatedPacketTransport * destination)122 void SetDestination(SimulatedPacketTransport* destination) {
123 RTC_DCHECK_RUN_ON(transport_thread_);
124 if (destination == this) {
125 return;
126 }
127 destination_ = destination;
128 SignalWritableState(this);
129 }
130
131 private:
132 const std::string transport_name_;
133 rtc::Thread* const transport_thread_;
134 const uint8_t packet_loss_percents_;
135 const uint16_t avg_send_delay_millis_;
136 std::atomic<SimulatedPacketTransport*> destination_ ATOMIC_VAR_INIT(nullptr);
137 rtc::AsyncInvoker invoker_;
138 webrtc::Random random_;
139 RTC_DISALLOW_COPY_AND_ASSIGN(SimulatedPacketTransport);
140 };
141
142 /**
143 * A helper class to send specified number of messages
144 * over SctpTransport with SCTP reliability settings
145 * provided by user. The reliability settings are specified
146 * by passing a template instance of SendDataParams.
147 * When .sid field inside SendDataParams is specified to
148 * negative value it means that actual .sid will be
149 * assigned by sender itself, .sid will be assigned from
150 * range [cricket::kMinSctpSid; cricket::kMaxSctpSid].
151 * The wide range of sids are used to possibly trigger
152 * more execution paths inside usrsctp.
153 */
154 class SctpDataSender final {
155 public:
SctpDataSender(rtc::Thread * thread,cricket::SctpTransport * transport,uint64_t target_messages_count,cricket::SendDataParams send_params,uint32_t sender_id)156 SctpDataSender(rtc::Thread* thread,
157 cricket::SctpTransport* transport,
158 uint64_t target_messages_count,
159 cricket::SendDataParams send_params,
160 uint32_t sender_id)
161 : thread_(thread),
162 transport_(transport),
163 target_messages_count_(target_messages_count),
164 send_params_(send_params),
165 sender_id_(sender_id) {
166 RTC_DCHECK(thread_);
167 RTC_DCHECK(transport_);
168 }
169
Start()170 void Start() {
171 invoker_.AsyncInvoke<void>(RTC_FROM_HERE, thread_, [this] {
172 if (started_) {
173 RTC_LOG(LS_INFO) << sender_id_ << " sender is already started";
174 return;
175 }
176 started_ = true;
177 SendNextMessage();
178 });
179 }
180
BytesSentCount() const181 uint64_t BytesSentCount() const { return num_bytes_sent_; }
182
MessagesSentCount() const183 uint64_t MessagesSentCount() const { return num_messages_sent_; }
184
GetLastError()185 absl::optional<std::string> GetLastError() {
186 absl::optional<std::string> result = absl::nullopt;
187 thread_->Invoke<void>(RTC_FROM_HERE,
188 [this, &result] { result = last_error_; });
189 return result;
190 }
191
WaitForCompletion(int give_up_after_ms)192 bool WaitForCompletion(int give_up_after_ms) {
193 return sent_target_messages_count_.Wait(give_up_after_ms, kDefaultTimeout);
194 }
195
196 private:
SendNextMessage()197 void SendNextMessage() {
198 RTC_DCHECK_RUN_ON(thread_);
199 if (!started_ || num_messages_sent_ >= target_messages_count_) {
200 sent_target_messages_count_.Set();
201 return;
202 }
203
204 if (num_messages_sent_ % kLogPerMessagesCount == 0) {
205 RTC_LOG(LS_INFO) << sender_id_ << " sender will try send message "
206 << (num_messages_sent_ + 1) << " out of "
207 << target_messages_count_;
208 }
209
210 cricket::SendDataParams params(send_params_);
211 if (params.sid < 0) {
212 params.sid = cricket::kMinSctpSid +
213 (num_messages_sent_ % cricket::kMaxSctpStreams);
214 }
215
216 cricket::SendDataResult result;
217 transport_->SendData(params, payload_, &result);
218 switch (result) {
219 case cricket::SDR_BLOCK:
220 // retry after timeout
221 invoker_.AsyncInvokeDelayed<void>(
222 RTC_FROM_HERE, thread_,
223 rtc::Bind(&SctpDataSender::SendNextMessage, this), 500);
224 break;
225 case cricket::SDR_SUCCESS:
226 // send next
227 num_bytes_sent_ += payload_.size();
228 ++num_messages_sent_;
229 invoker_.AsyncInvoke<void>(
230 RTC_FROM_HERE, thread_,
231 rtc::Bind(&SctpDataSender::SendNextMessage, this));
232 break;
233 case cricket::SDR_ERROR:
234 // give up
235 last_error_ = "SctpTransport::SendData error returned";
236 sent_target_messages_count_.Set();
237 break;
238 }
239 }
240
241 rtc::Thread* const thread_;
242 cricket::SctpTransport* const transport_;
243 const uint64_t target_messages_count_;
244 const cricket::SendDataParams send_params_;
245 const uint32_t sender_id_;
246 rtc::CopyOnWriteBuffer payload_{std::string(1400, '.').c_str(), 1400};
247 std::atomic<bool> started_ ATOMIC_VAR_INIT(false);
248 rtc::AsyncInvoker invoker_;
249 std::atomic<uint64_t> num_messages_sent_ ATOMIC_VAR_INIT(0);
250 rtc::Event sent_target_messages_count_{true, false};
251 std::atomic<uint64_t> num_bytes_sent_ ATOMIC_VAR_INIT(0);
252 absl::optional<std::string> last_error_;
253 RTC_DISALLOW_COPY_AND_ASSIGN(SctpDataSender);
254 };
255
256 /**
257 * A helper class which counts number of received messages
258 * and bytes over SctpTransport. Also allow waiting until
259 * specified number of messages received.
260 */
261 class SctpDataReceiver final : public sigslot::has_slots<> {
262 public:
SctpDataReceiver(uint32_t receiver_id,uint64_t target_messages_count)263 explicit SctpDataReceiver(uint32_t receiver_id,
264 uint64_t target_messages_count)
265 : receiver_id_(receiver_id),
266 target_messages_count_(target_messages_count) {}
267
OnDataReceived(const cricket::ReceiveDataParams & params,const rtc::CopyOnWriteBuffer & data)268 void OnDataReceived(const cricket::ReceiveDataParams& params,
269 const rtc::CopyOnWriteBuffer& data) {
270 num_bytes_received_ += data.size();
271 if (++num_messages_received_ == target_messages_count_) {
272 received_target_messages_count_.Set();
273 }
274
275 if (num_messages_received_ % kLogPerMessagesCount == 0) {
276 RTC_LOG(INFO) << receiver_id_ << " receiver got "
277 << num_messages_received_ << " messages";
278 }
279 }
280
MessagesReceivedCount() const281 uint64_t MessagesReceivedCount() const { return num_messages_received_; }
282
BytesReceivedCount() const283 uint64_t BytesReceivedCount() const { return num_bytes_received_; }
284
WaitForMessagesReceived(int timeout_millis)285 bool WaitForMessagesReceived(int timeout_millis) {
286 return received_target_messages_count_.Wait(timeout_millis);
287 }
288
289 private:
290 std::atomic<uint64_t> num_messages_received_ ATOMIC_VAR_INIT(0);
291 std::atomic<uint64_t> num_bytes_received_ ATOMIC_VAR_INIT(0);
292 rtc::Event received_target_messages_count_{true, false};
293 const uint32_t receiver_id_;
294 const uint64_t target_messages_count_;
295 RTC_DISALLOW_COPY_AND_ASSIGN(SctpDataReceiver);
296 };
297
298 /**
299 * Simple class to manage set of threads.
300 */
301 class ThreadPool final {
302 public:
ThreadPool(size_t threads_count)303 explicit ThreadPool(size_t threads_count) : random_(42) {
304 RTC_DCHECK(threads_count > 0);
305 threads_.reserve(threads_count);
306 for (size_t i = 0; i < threads_count; i++) {
307 auto thread = rtc::Thread::Create();
308 thread->SetName("Thread #" + rtc::ToString(i + 1) + " from Pool", this);
309 thread->Start();
310 threads_.emplace_back(std::move(thread));
311 }
312 }
313
GetRandomThread()314 rtc::Thread* GetRandomThread() {
315 return threads_[random_.Rand(0U, threads_.size() - 1)].get();
316 }
317
318 private:
319 webrtc::Random random_;
320 std::vector<std::unique_ptr<rtc::Thread>> threads_;
321 RTC_DISALLOW_COPY_AND_ASSIGN(ThreadPool);
322 };
323
324 /**
325 * Represents single ping-pong test over SctpTransport.
326 * User can specify target number of message for bidirectional
327 * send, underlying transport packets loss and average packet delay
328 * and SCTP delivery settings.
329 */
330 class SctpPingPong final {
331 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,cricket::SendDataParams send_params)332 SctpPingPong(uint32_t id,
333 uint16_t port1,
334 uint16_t port2,
335 rtc::Thread* transport_thread1,
336 rtc::Thread* transport_thread2,
337 uint32_t messages_count,
338 uint8_t packet_loss_percents,
339 uint16_t avg_send_delay_millis,
340 cricket::SendDataParams send_params)
341 : id_(id),
342 port1_(port1),
343 port2_(port2),
344 transport_thread1_(transport_thread1),
345 transport_thread2_(transport_thread2),
346 messages_count_(messages_count),
347 packet_loss_percents_(packet_loss_percents),
348 avg_send_delay_millis_(avg_send_delay_millis),
349 send_params_(send_params) {
350 RTC_DCHECK(transport_thread1_ != nullptr);
351 RTC_DCHECK(transport_thread2_ != nullptr);
352 }
353
~SctpPingPong()354 virtual ~SctpPingPong() {
355 transport_thread1_->Invoke<void>(RTC_FROM_HERE, [this] {
356 data_sender1_.reset();
357 sctp_transport1_->SetDtlsTransport(nullptr);
358 packet_transport1_->SetDestination(nullptr);
359 });
360 transport_thread2_->Invoke<void>(RTC_FROM_HERE, [this] {
361 data_sender2_.reset();
362 sctp_transport2_->SetDtlsTransport(nullptr);
363 packet_transport2_->SetDestination(nullptr);
364 });
365 transport_thread1_->Invoke<void>(RTC_FROM_HERE, [this] {
366 sctp_transport1_.reset();
367 data_receiver1_.reset();
368 packet_transport1_.reset();
369 });
370 transport_thread2_->Invoke<void>(RTC_FROM_HERE, [this] {
371 sctp_transport2_.reset();
372 data_receiver2_.reset();
373 packet_transport2_.reset();
374 });
375 }
376
Start()377 bool Start() {
378 CreateTwoConnectedSctpTransportsWithAllStreams();
379
380 {
381 webrtc::MutexLock lock(&lock_);
382 if (!errors_list_.empty()) {
383 return false;
384 }
385 }
386
387 data_sender1_.reset(new SctpDataSender(transport_thread1_,
388 sctp_transport1_.get(),
389 messages_count_, send_params_, id_));
390 data_sender2_.reset(new SctpDataSender(transport_thread2_,
391 sctp_transport2_.get(),
392 messages_count_, send_params_, id_));
393 data_sender1_->Start();
394 data_sender2_->Start();
395 return true;
396 }
397
GetErrorsList() const398 std::vector<std::string> GetErrorsList() const {
399 std::vector<std::string> result;
400 {
401 webrtc::MutexLock lock(&lock_);
402 result = errors_list_;
403 }
404 return result;
405 }
406
WaitForCompletion(int32_t timeout_millis)407 void WaitForCompletion(int32_t timeout_millis) {
408 if (data_sender1_ == nullptr) {
409 ReportError("SctpPingPong id = " + rtc::ToString(id_) +
410 ", sender 1 is not created");
411 return;
412 }
413 if (data_sender2_ == nullptr) {
414 ReportError("SctpPingPong id = " + rtc::ToString(id_) +
415 ", sender 2 is not created");
416 return;
417 }
418
419 if (!data_sender1_->WaitForCompletion(timeout_millis)) {
420 ReportError("SctpPingPong id = " + rtc::ToString(id_) +
421 ", sender 1 failed to complete within " +
422 rtc::ToString(timeout_millis) + " millis");
423 return;
424 }
425
426 auto sender1_error = data_sender1_->GetLastError();
427 if (sender1_error.has_value()) {
428 ReportError("SctpPingPong id = " + rtc::ToString(id_) +
429 ", sender 1 error: " + sender1_error.value());
430 return;
431 }
432
433 if (!data_sender2_->WaitForCompletion(timeout_millis)) {
434 ReportError("SctpPingPong id = " + rtc::ToString(id_) +
435 ", sender 2 failed to complete within " +
436 rtc::ToString(timeout_millis) + " millis");
437 return;
438 }
439
440 auto sender2_error = data_sender2_->GetLastError();
441 if (sender2_error.has_value()) {
442 ReportError("SctpPingPong id = " + rtc::ToString(id_) +
443 ", sender 2 error: " + sender1_error.value());
444 return;
445 }
446
447 if ((data_sender1_->MessagesSentCount() != messages_count_)) {
448 ReportError("SctpPingPong id = " + rtc::ToString(id_) +
449 ", sender 1 sent only " +
450 rtc::ToString(data_sender1_->MessagesSentCount()) +
451 " out of " + rtc::ToString(messages_count_));
452 return;
453 }
454
455 if ((data_sender2_->MessagesSentCount() != messages_count_)) {
456 ReportError("SctpPingPong id = " + rtc::ToString(id_) +
457 ", sender 2 sent only " +
458 rtc::ToString(data_sender2_->MessagesSentCount()) +
459 " out of " + rtc::ToString(messages_count_));
460 return;
461 }
462
463 if (!data_receiver1_->WaitForMessagesReceived(timeout_millis)) {
464 ReportError("SctpPingPong id = " + rtc::ToString(id_) +
465 ", receiver 1 did not complete within " +
466 rtc::ToString(messages_count_));
467 return;
468 }
469
470 if (!data_receiver2_->WaitForMessagesReceived(timeout_millis)) {
471 ReportError("SctpPingPong id = " + rtc::ToString(id_) +
472 ", receiver 2 did not complete within " +
473 rtc::ToString(messages_count_));
474 return;
475 }
476
477 if (data_receiver1_->BytesReceivedCount() !=
478 data_sender2_->BytesSentCount()) {
479 ReportError(
480 "SctpPingPong id = " + rtc::ToString(id_) + ", receiver 1 received " +
481 rtc::ToString(data_receiver1_->BytesReceivedCount()) +
482 " bytes, but sender 2 send " +
483 rtc::ToString(rtc::ToString(data_sender2_->BytesSentCount())));
484 return;
485 }
486
487 if (data_receiver2_->BytesReceivedCount() !=
488 data_sender1_->BytesSentCount()) {
489 ReportError(
490 "SctpPingPong id = " + rtc::ToString(id_) + ", receiver 2 received " +
491 rtc::ToString(data_receiver2_->BytesReceivedCount()) +
492 " bytes, but sender 1 send " +
493 rtc::ToString(rtc::ToString(data_sender1_->BytesSentCount())));
494 return;
495 }
496
497 RTC_LOG(LS_INFO) << "SctpPingPong id = " << id_ << " is done";
498 }
499
500 private:
CreateTwoConnectedSctpTransportsWithAllStreams()501 void CreateTwoConnectedSctpTransportsWithAllStreams() {
502 transport_thread1_->Invoke<void>(RTC_FROM_HERE, [this] {
503 packet_transport1_.reset(new SimulatedPacketTransport(
504 "SctpPingPong id = " + rtc::ToString(id_) + ", packet transport 1",
505 transport_thread1_, packet_loss_percents_, avg_send_delay_millis_));
506 data_receiver1_.reset(new SctpDataReceiver(id_, messages_count_));
507 sctp_transport1_.reset(new cricket::SctpTransport(
508 transport_thread1_, packet_transport1_.get()));
509 sctp_transport1_->set_debug_name_for_testing("sctp transport 1");
510
511 sctp_transport1_->SignalDataReceived.connect(
512 data_receiver1_.get(), &SctpDataReceiver::OnDataReceived);
513
514 for (uint32_t i = cricket::kMinSctpSid; i <= cricket::kMaxSctpSid; i++) {
515 if (!sctp_transport1_->OpenStream(i)) {
516 ReportError("SctpPingPong id = " + rtc::ToString(id_) +
517 ", sctp transport 1 stream " + rtc::ToString(i) +
518 " failed to open");
519 break;
520 }
521 }
522 });
523
524 transport_thread2_->Invoke<void>(RTC_FROM_HERE, [this] {
525 packet_transport2_.reset(new SimulatedPacketTransport(
526 "SctpPingPong id = " + rtc::ToString(id_) + "packet transport 2",
527 transport_thread2_, packet_loss_percents_, avg_send_delay_millis_));
528 data_receiver2_.reset(new SctpDataReceiver(id_, messages_count_));
529 sctp_transport2_.reset(new cricket::SctpTransport(
530 transport_thread2_, packet_transport2_.get()));
531 sctp_transport2_->set_debug_name_for_testing("sctp transport 2");
532 sctp_transport2_->SignalDataReceived.connect(
533 data_receiver2_.get(), &SctpDataReceiver::OnDataReceived);
534
535 for (uint32_t i = cricket::kMinSctpSid; i <= cricket::kMaxSctpSid; i++) {
536 if (!sctp_transport2_->OpenStream(i)) {
537 ReportError("SctpPingPong id = " + rtc::ToString(id_) +
538 ", sctp transport 2 stream " + rtc::ToString(i) +
539 " failed to open");
540 break;
541 }
542 }
543 });
544
545 transport_thread1_->Invoke<void>(RTC_FROM_HERE, [this] {
546 packet_transport1_->SetDestination(packet_transport2_.get());
547 });
548 transport_thread2_->Invoke<void>(RTC_FROM_HERE, [this] {
549 packet_transport2_->SetDestination(packet_transport1_.get());
550 });
551
552 transport_thread1_->Invoke<void>(RTC_FROM_HERE, [this] {
553 if (!sctp_transport1_->Start(port1_, port2_,
554 cricket::kSctpSendBufferSize)) {
555 ReportError("SctpPingPong id = " + rtc::ToString(id_) +
556 ", failed to start sctp transport 1");
557 }
558 });
559
560 transport_thread2_->Invoke<void>(RTC_FROM_HERE, [this] {
561 if (!sctp_transport2_->Start(port2_, port1_,
562 cricket::kSctpSendBufferSize)) {
563 ReportError("SctpPingPong id = " + rtc::ToString(id_) +
564 ", failed to start sctp transport 2");
565 }
566 });
567 }
568
ReportError(std::string error)569 void ReportError(std::string error) {
570 webrtc::MutexLock lock(&lock_);
571 errors_list_.push_back(std::move(error));
572 }
573
574 std::unique_ptr<SimulatedPacketTransport> packet_transport1_;
575 std::unique_ptr<SimulatedPacketTransport> packet_transport2_;
576 std::unique_ptr<SctpDataReceiver> data_receiver1_;
577 std::unique_ptr<SctpDataReceiver> data_receiver2_;
578 std::unique_ptr<cricket::SctpTransport> sctp_transport1_;
579 std::unique_ptr<cricket::SctpTransport> sctp_transport2_;
580 std::unique_ptr<SctpDataSender> data_sender1_;
581 std::unique_ptr<SctpDataSender> data_sender2_;
582 mutable webrtc::Mutex lock_;
583 std::vector<std::string> errors_list_ RTC_GUARDED_BY(lock_);
584
585 const uint32_t id_;
586 const uint16_t port1_;
587 const uint16_t port2_;
588 rtc::Thread* const transport_thread1_;
589 rtc::Thread* const transport_thread2_;
590 const uint32_t messages_count_;
591 const uint8_t packet_loss_percents_;
592 const uint16_t avg_send_delay_millis_;
593 const cricket::SendDataParams send_params_;
594 RTC_DISALLOW_COPY_AND_ASSIGN(SctpPingPong);
595 };
596
597 /**
598 * Helper function to calculate max number of milliseconds
599 * allowed for test to run based on test configuration.
600 */
GetExecutionTimeLimitInMillis(uint32_t total_messages,uint8_t packet_loss_percents)601 constexpr int32_t GetExecutionTimeLimitInMillis(uint32_t total_messages,
602 uint8_t packet_loss_percents) {
603 return std::min<int64_t>(
604 std::numeric_limits<int32_t>::max(),
605 std::max<int64_t>(
606 1LL * total_messages * 100 *
607 std::max(1, packet_loss_percents * packet_loss_percents),
608 kDefaultTimeout));
609 }
610
611 } // namespace
612
613 namespace cricket {
614
615 /**
616 * The set of tests intended to check usrsctp reliability on
617 * stress conditions: multiple sockets, concurrent access,
618 * lossy network link. It was observed in the past that
619 * usrsctp might misbehave in concurrent environment
620 * under load on lossy networks: deadlocks and memory corruption
621 * issues might happen in non-basic usage scenarios.
622 * It's recommended to run this test whenever usrsctp version
623 * used is updated to verify it properly works in stress
624 * conditions under higher than usual load.
625 * It is also recommended to enable ASAN when these tests
626 * are executed, so whenever memory bug is happen inside usrsctp,
627 * it will be easier to understand what went wrong with ASAN
628 * provided diagnostics information.
629 * The tests cases currently disabled by default due to
630 * long execution time and due to unresolved issue inside
631 * `usrsctp` library detected by try-bots with ThreadSanitizer.
632 */
633 class UsrSctpReliabilityTest : public ::testing::Test {};
634
635 /**
636 * A simple test which send multiple messages over reliable
637 * connection, usefull to verify test infrastructure works.
638 * Execution time is less than 1 second.
639 */
TEST_F(UsrSctpReliabilityTest,DISABLED_AllMessagesAreDeliveredOverReliableConnection)640 TEST_F(UsrSctpReliabilityTest,
641 DISABLED_AllMessagesAreDeliveredOverReliableConnection) {
642 auto thread1 = rtc::Thread::Create();
643 auto thread2 = rtc::Thread::Create();
644 thread1->Start();
645 thread2->Start();
646 constexpr uint8_t packet_loss_percents = 0;
647 constexpr uint16_t avg_send_delay_millis = 10;
648 constexpr uint32_t messages_count = 100;
649 constexpr int32_t wait_timeout =
650 GetExecutionTimeLimitInMillis(messages_count, packet_loss_percents);
651 static_assert(wait_timeout > 0,
652 "Timeout computation must produce positive value");
653
654 cricket::SendDataParams send_params;
655 send_params.sid = -1;
656 send_params.ordered = true;
657 send_params.reliable = true;
658 send_params.max_rtx_count = 0;
659 send_params.max_rtx_ms = 0;
660
661 SctpPingPong test(1, kTransport1Port, kTransport2Port, thread1.get(),
662 thread2.get(), messages_count, packet_loss_percents,
663 avg_send_delay_millis, send_params);
664 EXPECT_TRUE(test.Start()) << rtc::join(test.GetErrorsList(), ';');
665 test.WaitForCompletion(wait_timeout);
666 auto errors_list = test.GetErrorsList();
667 EXPECT_TRUE(errors_list.empty()) << rtc::join(errors_list, ';');
668 }
669
670 /**
671 * A test to verify that multiple messages can be reliably delivered
672 * over lossy network when usrsctp configured to guarantee reliably
673 * and in order delivery.
674 * The test case is disabled by default because it takes
675 * long time to run.
676 * Execution time is about 2.5 minutes.
677 */
TEST_F(UsrSctpReliabilityTest,DISABLED_AllMessagesAreDeliveredOverLossyConnectionReliableAndInOrder)678 TEST_F(UsrSctpReliabilityTest,
679 DISABLED_AllMessagesAreDeliveredOverLossyConnectionReliableAndInOrder) {
680 auto thread1 = rtc::Thread::Create();
681 auto thread2 = rtc::Thread::Create();
682 thread1->Start();
683 thread2->Start();
684 constexpr uint8_t packet_loss_percents = 5;
685 constexpr uint16_t avg_send_delay_millis = 16;
686 constexpr uint32_t messages_count = 10000;
687 constexpr int32_t wait_timeout =
688 GetExecutionTimeLimitInMillis(messages_count, packet_loss_percents);
689 static_assert(wait_timeout > 0,
690 "Timeout computation must produce positive value");
691
692 cricket::SendDataParams send_params;
693 send_params.sid = -1;
694 send_params.ordered = true;
695 send_params.reliable = true;
696 send_params.max_rtx_count = 0;
697 send_params.max_rtx_ms = 0;
698
699 SctpPingPong test(1, kTransport1Port, kTransport2Port, thread1.get(),
700 thread2.get(), messages_count, packet_loss_percents,
701 avg_send_delay_millis, send_params);
702
703 EXPECT_TRUE(test.Start()) << rtc::join(test.GetErrorsList(), ';');
704 test.WaitForCompletion(wait_timeout);
705 auto errors_list = test.GetErrorsList();
706 EXPECT_TRUE(errors_list.empty()) << rtc::join(errors_list, ';');
707 }
708
709 /**
710 * A test to verify that multiple messages can be reliably delivered
711 * over lossy network when usrsctp configured to retransmit lost
712 * packets.
713 * The test case is disabled by default because it takes
714 * long time to run.
715 * Execution time is about 2.5 minutes.
716 */
TEST_F(UsrSctpReliabilityTest,DISABLED_AllMessagesAreDeliveredOverLossyConnectionWithRetries)717 TEST_F(UsrSctpReliabilityTest,
718 DISABLED_AllMessagesAreDeliveredOverLossyConnectionWithRetries) {
719 auto thread1 = rtc::Thread::Create();
720 auto thread2 = rtc::Thread::Create();
721 thread1->Start();
722 thread2->Start();
723 constexpr uint8_t packet_loss_percents = 5;
724 constexpr uint16_t avg_send_delay_millis = 16;
725 constexpr uint32_t messages_count = 10000;
726 constexpr int32_t wait_timeout =
727 GetExecutionTimeLimitInMillis(messages_count, packet_loss_percents);
728 static_assert(wait_timeout > 0,
729 "Timeout computation must produce positive value");
730
731 cricket::SendDataParams send_params;
732 send_params.sid = -1;
733 send_params.ordered = false;
734 send_params.reliable = false;
735 send_params.max_rtx_count = INT_MAX;
736 send_params.max_rtx_ms = INT_MAX;
737
738 SctpPingPong test(1, kTransport1Port, kTransport2Port, thread1.get(),
739 thread2.get(), messages_count, packet_loss_percents,
740 avg_send_delay_millis, send_params);
741
742 EXPECT_TRUE(test.Start()) << rtc::join(test.GetErrorsList(), ';');
743 test.WaitForCompletion(wait_timeout);
744 auto errors_list = test.GetErrorsList();
745 EXPECT_TRUE(errors_list.empty()) << rtc::join(errors_list, ';');
746 }
747
748 /**
749 * This is kind of reliability stress-test of usrsctp to verify
750 * that all messages are delivered when multiple usrsctp
751 * sockets used concurrently and underlying transport is lossy.
752 *
753 * It was observed in the past that in stress condtions usrsctp
754 * might encounter deadlock and memory corruption bugs:
755 * https://github.com/sctplab/usrsctp/issues/325
756 *
757 * It is recoomended to run this test whenever usrsctp version
758 * used by WebRTC is updated.
759 *
760 * The test case is disabled by default because it takes
761 * long time to run.
762 * Execution time of this test is about 1-2 hours.
763 */
TEST_F(UsrSctpReliabilityTest,DISABLED_AllMessagesAreDeliveredOverLossyConnectionConcurrentTests)764 TEST_F(UsrSctpReliabilityTest,
765 DISABLED_AllMessagesAreDeliveredOverLossyConnectionConcurrentTests) {
766 ThreadPool pool(16);
767
768 cricket::SendDataParams send_params;
769 send_params.sid = -1;
770 send_params.ordered = true;
771 send_params.reliable = true;
772 send_params.max_rtx_count = 0;
773 send_params.max_rtx_ms = 0;
774 constexpr uint32_t base_sctp_port = 5000;
775
776 // The constants value below were experimentally chosen
777 // to have reasonable execution time and to reproduce
778 // particular deadlock issue inside usrsctp:
779 // https://github.com/sctplab/usrsctp/issues/325
780 // The constants values may be adjusted next time
781 // some other issue inside usrsctp need to be debugged.
782 constexpr uint32_t messages_count = 200;
783 constexpr uint8_t packet_loss_percents = 5;
784 constexpr uint16_t avg_send_delay_millis = 0;
785 constexpr uint32_t parallel_ping_pongs = 16 * 1024;
786 constexpr uint32_t total_ping_pong_tests = 16 * parallel_ping_pongs;
787
788 constexpr int32_t wait_timeout = GetExecutionTimeLimitInMillis(
789 total_ping_pong_tests * messages_count, packet_loss_percents);
790 static_assert(wait_timeout > 0,
791 "Timeout computation must produce positive value");
792
793 std::queue<std::unique_ptr<SctpPingPong>> tests;
794
795 for (uint32_t i = 0; i < total_ping_pong_tests; i++) {
796 uint32_t port1 =
797 base_sctp_port + (2 * i) % (UINT16_MAX - base_sctp_port - 1);
798
799 auto test = std::make_unique<SctpPingPong>(
800 i, port1, port1 + 1, pool.GetRandomThread(), pool.GetRandomThread(),
801 messages_count, packet_loss_percents, avg_send_delay_millis,
802 send_params);
803
804 EXPECT_TRUE(test->Start()) << rtc::join(test->GetErrorsList(), ';');
805 tests.emplace(std::move(test));
806
807 while (tests.size() >= parallel_ping_pongs) {
808 auto& oldest_test = tests.front();
809 oldest_test->WaitForCompletion(wait_timeout);
810
811 auto errors_list = oldest_test->GetErrorsList();
812 EXPECT_TRUE(errors_list.empty()) << rtc::join(errors_list, ';');
813 tests.pop();
814 }
815 }
816
817 while (!tests.empty()) {
818 auto& oldest_test = tests.front();
819 oldest_test->WaitForCompletion(wait_timeout);
820
821 auto errors_list = oldest_test->GetErrorsList();
822 EXPECT_TRUE(errors_list.empty()) << rtc::join(errors_list, ';');
823 tests.pop();
824 }
825 }
826
827 } // namespace cricket
828