From 27e50ccf4c09d595d8d24839d242d9bfe3817081 Mon Sep 17 00:00:00 2001 From: Victor Boivie Date: Mon, 5 Apr 2021 08:28:42 +0200 Subject: [PATCH] dcsctp: Add Retransmission Timeout The socket can measure the round-trip-time (RTT) by two different scenarios: * When a sent data is ACKed * When a HEARTBEAT has been sent, which as been ACKed. The RTT will be used to calculate which timeout value that should be used for e.g. the retransmission timer (T3-RTX). On connections with a low RTT, the RTO value will be low, and on a connection with high RTT, the RTO value will be high. And on a connection with a generally low RTT value, but where it varies a lot, the RTO value will be calculated to be fairly high, to not fire unnecessarily. So jitter is bad, and is part of the calculation. Bug: webrtc:12614 Change-Id: I64905ad566d5032d0428cd84143a9397355bbe9f Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/214045 Commit-Queue: Victor Boivie Reviewed-by: Harald Alvestrand Cr-Commit-Position: refs/heads/master@{#33832} --- net/dcsctp/public/dcsctp_options.h | 6 + net/dcsctp/tx/BUILD.gn | 13 ++ net/dcsctp/tx/retransmission_timeout.cc | 64 +++++++++ net/dcsctp/tx/retransmission_timeout.h | 58 ++++++++ net/dcsctp/tx/retransmission_timeout_test.cc | 136 +++++++++++++++++++ 5 files changed, 277 insertions(+) create mode 100644 net/dcsctp/tx/retransmission_timeout.cc create mode 100644 net/dcsctp/tx/retransmission_timeout.h create mode 100644 net/dcsctp/tx/retransmission_timeout_test.cc diff --git a/net/dcsctp/public/dcsctp_options.h b/net/dcsctp/public/dcsctp_options.h index acc237a0d5..9d6c9dc481 100644 --- a/net/dcsctp/public/dcsctp_options.h +++ b/net/dcsctp/public/dcsctp_options.h @@ -61,6 +61,12 @@ struct DcSctpOptions { // this before sending it. size_t max_send_buffer_size = 2 * 1024 * 1024; + // Max allowed RTT value. When the RTT is measured and it's found to be larger + // than this value, it will be discarded and not used for e.g. any RTO + // calculation. The default value is an extreme maximum but can be adapted + // to better match the environment. + DurationMs rtt_max = DurationMs(8'000); + // Initial RTO value. DurationMs rto_initial = DurationMs(500); diff --git a/net/dcsctp/tx/BUILD.gn b/net/dcsctp/tx/BUILD.gn index 3d20101da7..dd811eb0a1 100644 --- a/net/dcsctp/tx/BUILD.gn +++ b/net/dcsctp/tx/BUILD.gn @@ -41,6 +41,17 @@ rtc_library("retransmission_error_counter") { ] } +rtc_library("retransmission_timeout") { + deps = [ + "../../../rtc_base:checks", + "../../../rtc_base:rtc_base_approved", + ] + sources = [ + "retransmission_timeout.cc", + "retransmission_timeout.h", + ] +} + if (rtc_include_tests) { rtc_source_set("mock_send_queue") { testonly = true @@ -54,6 +65,7 @@ if (rtc_include_tests) { deps = [ ":fcfs_send_queue", ":retransmission_error_counter", + ":retransmission_timeout", "../../../api:array_view", "../../../rtc_base:checks", "../../../rtc_base:gunit_helpers", @@ -63,6 +75,7 @@ if (rtc_include_tests) { sources = [ "fcfs_send_queue_test.cc", "retransmission_error_counter_test.cc", + "retransmission_timeout_test.cc", ] } } diff --git a/net/dcsctp/tx/retransmission_timeout.cc b/net/dcsctp/tx/retransmission_timeout.cc new file mode 100644 index 0000000000..f38b94d32c --- /dev/null +++ b/net/dcsctp/tx/retransmission_timeout.cc @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2021 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 "net/dcsctp/tx/retransmission_timeout.h" + +#include +#include + +#include "net/dcsctp/public/dcsctp_options.h" + +namespace dcsctp { +namespace { +// https://tools.ietf.org/html/rfc4960#section-15 +constexpr double kRtoAlpha = 0.125; +constexpr double kRtoBeta = 0.25; +} // namespace + +RetransmissionTimeout::RetransmissionTimeout(const DcSctpOptions& options) + : min_rto_(*options.rto_min), + max_rto_(*options.rto_max), + max_rtt_(*options.rtt_max), + rto_(*options.rto_initial) {} + +void RetransmissionTimeout::ObserveRTT(DurationMs measured_rtt) { + double rtt = *measured_rtt; + + // Unrealistic values will be skipped. If a wrongly measured (or otherwise + // corrupt) value was processed, it could change the state in a way that would + // take a very long time to recover. + if (rtt < 0.0 || rtt > max_rtt_) { + return; + } + + if (first_measurement_) { + // https://tools.ietf.org/html/rfc4960#section-6.3.1 + // "When the first RTT measurement R is made, set + // SRTT <- R, + // RTTVAR <- R/2, and + // RTO <- SRTT + 4 * RTTVAR." + srtt_ = rtt; + rttvar_ = rtt * 0.5; + rto_ = srtt_ + 4 * rttvar_; + first_measurement_ = false; + } else { + // https://tools.ietf.org/html/rfc4960#section-6.3.1 + // "When a new RTT measurement R' is made, set + // RTTVAR <- (1 - RTO.Beta) * RTTVAR + RTO.Beta * |SRTT - R'| + // SRTT <- (1 - RTO.Alpha) * SRTT + RTO.Alpha * R' + // RTO <- SRTT + 4 * RTTVAR." + rttvar_ = (1 - kRtoBeta) * rttvar_ + kRtoBeta * std::abs(srtt_ - rtt); + srtt_ = (1 - kRtoAlpha) * srtt_ + kRtoAlpha * rtt; + rto_ = srtt_ + 4 * rttvar_; + } + + // Clamp RTO between min and max. + rto_ = std::fmin(std::fmax(rto_, min_rto_), max_rto_); +} +} // namespace dcsctp diff --git a/net/dcsctp/tx/retransmission_timeout.h b/net/dcsctp/tx/retransmission_timeout.h new file mode 100644 index 0000000000..0fac33e59c --- /dev/null +++ b/net/dcsctp/tx/retransmission_timeout.h @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2021 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 NET_DCSCTP_TX_RETRANSMISSION_TIMEOUT_H_ +#define NET_DCSCTP_TX_RETRANSMISSION_TIMEOUT_H_ + +#include +#include + +#include "net/dcsctp/public/dcsctp_options.h" + +namespace dcsctp { + +// Manages updating of the Retransmission Timeout (RTO) SCTP variable, which is +// used directly as the base timeout for T3-RTX and for other timers, such as +// delayed ack. +// +// When a round-trip-time (RTT) is calculated (outside this class), `Observe` +// is called, which calculates the retransmission timeout (RTO) value. The RTO +// value will become larger if the RTT is high and/or the RTT values are varying +// a lot, which is an indicator of a bad connection. +class RetransmissionTimeout { + public: + explicit RetransmissionTimeout(const DcSctpOptions& options); + + // To be called when a RTT has been measured, to update the RTO value. + void ObserveRTT(DurationMs measured_rtt); + + // Returns the Retransmission Timeout (RTO) value, in milliseconds. + DurationMs rto() const { return DurationMs(rto_); } + + // Returns the smoothed RTT value, in milliseconds. + DurationMs srtt() const { return DurationMs(srtt_); } + + private: + // Note that all intermediate state calculation is done in the floating point + // domain, to maintain precision. + const double min_rto_; + const double max_rto_; + const double max_rtt_; + // If this is the first measurement + bool first_measurement_ = true; + // Smoothed Round-Trip Time + double srtt_ = 0.0; + // Round-Trip Time Variation + double rttvar_ = 0.0; + // Retransmission Timeout + double rto_; +}; +} // namespace dcsctp + +#endif // NET_DCSCTP_TX_RETRANSMISSION_TIMEOUT_H_ diff --git a/net/dcsctp/tx/retransmission_timeout_test.cc b/net/dcsctp/tx/retransmission_timeout_test.cc new file mode 100644 index 0000000000..eb5e72e7ba --- /dev/null +++ b/net/dcsctp/tx/retransmission_timeout_test.cc @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2021 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 "net/dcsctp/tx/retransmission_timeout.h" + +#include "net/dcsctp/public/dcsctp_options.h" +#include "rtc_base/gunit.h" +#include "test/gmock.h" + +namespace dcsctp { +namespace { + +constexpr DurationMs kMaxRtt = DurationMs(8'000); +constexpr DurationMs kInitialRto = DurationMs(200); +constexpr DurationMs kMaxRto = DurationMs(800); +constexpr DurationMs kMinRto = DurationMs(120); + +DcSctpOptions MakeOptions() { + DcSctpOptions options; + options.rtt_max = kMaxRtt; + options.rto_initial = kInitialRto; + options.rto_max = kMaxRto; + options.rto_min = kMinRto; + return options; +} + +TEST(RetransmissionTimeoutTest, HasValidInitialRto) { + RetransmissionTimeout rto_(MakeOptions()); + EXPECT_EQ(rto_.rto(), kInitialRto); +} + +TEST(RetransmissionTimeoutTest, NegativeValuesDoNotAffectRTO) { + RetransmissionTimeout rto_(MakeOptions()); + // Initial negative value + rto_.ObserveRTT(DurationMs(-10)); + EXPECT_EQ(rto_.rto(), kInitialRto); + rto_.ObserveRTT(DurationMs(124)); + EXPECT_EQ(*rto_.rto(), 372); + // Subsequent negative value + rto_.ObserveRTT(DurationMs(-10)); + EXPECT_EQ(*rto_.rto(), 372); +} + +TEST(RetransmissionTimeoutTest, TooLargeValuesDoNotAffectRTO) { + RetransmissionTimeout rto_(MakeOptions()); + // Initial too large value + rto_.ObserveRTT(kMaxRtt + DurationMs(100)); + EXPECT_EQ(rto_.rto(), kInitialRto); + rto_.ObserveRTT(DurationMs(124)); + EXPECT_EQ(*rto_.rto(), 372); + // Subsequent too large value + rto_.ObserveRTT(kMaxRtt + DurationMs(100)); + EXPECT_EQ(*rto_.rto(), 372); +} + +TEST(RetransmissionTimeoutTest, WillNeverGoBelowMinimumRto) { + RetransmissionTimeout rto_(MakeOptions()); + for (int i = 0; i < 1000; ++i) { + rto_.ObserveRTT(DurationMs(1)); + } + EXPECT_GE(rto_.rto(), kMinRto); +} + +TEST(RetransmissionTimeoutTest, WillNeverGoAboveMaximumRto) { + RetransmissionTimeout rto_(MakeOptions()); + for (int i = 0; i < 1000; ++i) { + rto_.ObserveRTT(kMaxRtt - DurationMs(1)); + // Adding jitter, which would make it RTO be well above RTT. + rto_.ObserveRTT(kMaxRtt - DurationMs(100)); + } + EXPECT_LE(rto_.rto(), kMaxRto); +} + +TEST(RetransmissionTimeoutTest, CalculatesRtoForStableRtt) { + RetransmissionTimeout rto_(MakeOptions()); + rto_.ObserveRTT(DurationMs(124)); + EXPECT_THAT(*rto_.rto(), 372); + rto_.ObserveRTT(DurationMs(128)); + EXPECT_THAT(*rto_.rto(), 314); + rto_.ObserveRTT(DurationMs(123)); + EXPECT_THAT(*rto_.rto(), 268); + rto_.ObserveRTT(DurationMs(125)); + EXPECT_THAT(*rto_.rto(), 233); + rto_.ObserveRTT(DurationMs(127)); + EXPECT_THAT(*rto_.rto(), 208); +} + +TEST(RetransmissionTimeoutTest, CalculatesRtoForUnstableRtt) { + RetransmissionTimeout rto_(MakeOptions()); + rto_.ObserveRTT(DurationMs(124)); + EXPECT_THAT(*rto_.rto(), 372); + rto_.ObserveRTT(DurationMs(402)); + EXPECT_THAT(*rto_.rto(), 622); + rto_.ObserveRTT(DurationMs(728)); + EXPECT_THAT(*rto_.rto(), 800); + rto_.ObserveRTT(DurationMs(89)); + EXPECT_THAT(*rto_.rto(), 800); + rto_.ObserveRTT(DurationMs(126)); + EXPECT_THAT(*rto_.rto(), 800); +} + +TEST(RetransmissionTimeoutTest, WillStabilizeAfterAWhile) { + RetransmissionTimeout rto_(MakeOptions()); + rto_.ObserveRTT(DurationMs(124)); + rto_.ObserveRTT(DurationMs(402)); + rto_.ObserveRTT(DurationMs(728)); + rto_.ObserveRTT(DurationMs(89)); + rto_.ObserveRTT(DurationMs(126)); + EXPECT_THAT(*rto_.rto(), 800); + rto_.ObserveRTT(DurationMs(124)); + EXPECT_THAT(*rto_.rto(), 800); + rto_.ObserveRTT(DurationMs(122)); + EXPECT_THAT(*rto_.rto(), 709); + rto_.ObserveRTT(DurationMs(123)); + EXPECT_THAT(*rto_.rto(), 630); + rto_.ObserveRTT(DurationMs(124)); + EXPECT_THAT(*rto_.rto(), 561); + rto_.ObserveRTT(DurationMs(122)); + EXPECT_THAT(*rto_.rto(), 504); + rto_.ObserveRTT(DurationMs(124)); + EXPECT_THAT(*rto_.rto(), 453); + rto_.ObserveRTT(DurationMs(124)); + EXPECT_THAT(*rto_.rto(), 409); + rto_.ObserveRTT(DurationMs(124)); + EXPECT_THAT(*rto_.rto(), 372); + rto_.ObserveRTT(DurationMs(124)); + EXPECT_THAT(*rto_.rto(), 339); +} +} // namespace +} // namespace dcsctp