diff --git a/net/dcsctp/public/dcsctp_options.h b/net/dcsctp/public/dcsctp_options.h index 221a856686..8b4ed3c679 100644 --- a/net/dcsctp/public/dcsctp_options.h +++ b/net/dcsctp/public/dcsctp_options.h @@ -148,8 +148,11 @@ struct DcSctpOptions { // processing time of received packets and the clock granularity when setting // the delayed ack timer on the peer. // - // This is described for TCP in + // This is defined as "G" in the algorithm for TCP in // https://datatracker.ietf.org/doc/html/rfc6298#section-4. + // + // Note that this value will be further adjusted by scaling factors, so if you + // intend to change this, do it incrementally and measure the results. DurationMs min_rtt_variance = DurationMs(220); // The initial congestion window size, in number of MTUs. diff --git a/net/dcsctp/tx/retransmission_timeout.cc b/net/dcsctp/tx/retransmission_timeout.cc index 8af77041a5..3456ed2910 100644 --- a/net/dcsctp/tx/retransmission_timeout.cc +++ b/net/dcsctp/tx/retransmission_timeout.cc @@ -17,48 +17,50 @@ namespace dcsctp { +// https://datatracker.ietf.org/doc/html/rfc4960#section-15. +constexpr double kRtoAlpha = 0.125; +constexpr double kRtoBeta = 0.25; + +// A factor that the `min_rtt_variance` configuration option will be divided by +// (before later multiplied with K, which is 4 according to RFC6298). When this +// value was introduced, it was unintentionally divided by 8 since that code +// worked with scaled numbers (to avoid floating point math). That behavior is +// kept as downstream users have measured good values for their use-cases. +constexpr double kHeuristicVarianceAdjustment = 8.0; + RetransmissionTimeout::RetransmissionTimeout(const DcSctpOptions& options) : min_rto_(options.rto_min.ToTimeDelta()), max_rto_(options.rto_max.ToTimeDelta()), max_rtt_(options.rtt_max.ToTimeDelta()), - min_rtt_variance_(*options.min_rtt_variance), - scaled_srtt_(*options.rto_initial << kRttShift), - rto_(*options.rto_initial) {} + min_rtt_variance_(options.min_rtt_variance.ToTimeDelta() / + kHeuristicVarianceAdjustment), + srtt_(options.rto_initial.ToTimeDelta()), + rto_(options.rto_initial.ToTimeDelta()) {} -void RetransmissionTimeout::ObserveRTT(webrtc::TimeDelta measured_rtt) { +void RetransmissionTimeout::ObserveRTT(webrtc::TimeDelta 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 (measured_rtt < webrtc::TimeDelta::Zero() || measured_rtt > max_rtt_) { + if (rtt < webrtc::TimeDelta::Zero() || rtt > max_rtt_) { return; } - const int64_t rtt = measured_rtt.ms(); - - // From https://tools.ietf.org/html/rfc4960#section-6.3.1, but avoiding - // floating point math by implementing algorithm from "V. Jacobson: Congestion - // avoidance and control", but adapted for SCTP. + // https://tools.ietf.org/html/rfc4960#section-6.3.1. if (first_measurement_) { - scaled_srtt_ = rtt << kRttShift; - scaled_rtt_var_ = (rtt / 2) << kRttVarShift; + srtt_ = rtt; + rtt_var_ = rtt / 2; first_measurement_ = false; } else { - int64_t rtt_diff = rtt - (scaled_srtt_ >> kRttShift); - scaled_srtt_ += rtt_diff; - if (rtt_diff < 0) { - rtt_diff = -rtt_diff; - } - rtt_diff -= (scaled_rtt_var_ >> kRttVarShift); - scaled_rtt_var_ += rtt_diff; + webrtc::TimeDelta rtt_diff = (srtt_ - rtt).Abs(); + rtt_var_ = (1 - kRtoBeta) * rtt_var_ + kRtoBeta * rtt_diff; + srtt_ = (1 - kRtoAlpha) * srtt_ + kRtoAlpha * rtt; } - if (scaled_rtt_var_ < min_rtt_variance_) { - scaled_rtt_var_ = min_rtt_variance_; + if (rtt_var_ < min_rtt_variance_) { + rtt_var_ = min_rtt_variance_; } - rto_ = (scaled_srtt_ >> kRttShift) + scaled_rtt_var_; - - // Clamp RTO between min and max. - rto_ = std::min(std::max(rto_, min_rto_.ms()), max_rto_.ms()); + rto_ = srtt_ + 4 * rtt_var_; + rto_ = std::clamp(rto_, min_rto_, max_rto_); } } // namespace dcsctp diff --git a/net/dcsctp/tx/retransmission_timeout.h b/net/dcsctp/tx/retransmission_timeout.h index b4b0fd7fef..b87501d79f 100644 --- a/net/dcsctp/tx/retransmission_timeout.h +++ b/net/dcsctp/tx/retransmission_timeout.h @@ -27,34 +27,30 @@ namespace dcsctp { // a lot, which is an indicator of a bad connection. class RetransmissionTimeout { public: - static constexpr int kRttShift = 3; - static constexpr int kRttVarShift = 2; explicit RetransmissionTimeout(const DcSctpOptions& options); // To be called when a RTT has been measured, to update the RTO value. - void ObserveRTT(webrtc::TimeDelta measured_rtt); + void ObserveRTT(webrtc::TimeDelta rtt); - // Returns the Retransmission Timeout (RTO) value, in milliseconds. - webrtc::TimeDelta rto() const { return webrtc::TimeDelta::Millis(rto_); } + // Returns the Retransmission Timeout (RTO) value. + webrtc::TimeDelta rto() const { return rto_; } - // Returns the smoothed RTT value, in milliseconds. - webrtc::TimeDelta srtt() const { - return webrtc::TimeDelta::Millis(scaled_srtt_ >> kRttShift); - } + // Returns the smoothed RTT value. + webrtc::TimeDelta srtt() const { return srtt_; } private: const webrtc::TimeDelta min_rto_; const webrtc::TimeDelta max_rto_; const webrtc::TimeDelta max_rtt_; - const int64_t min_rtt_variance_; + const webrtc::TimeDelta min_rtt_variance_; // If this is the first measurement bool first_measurement_ = true; - // Smoothed Round-Trip Time, shifted by kRttShift - int64_t scaled_srtt_; - // Round-Trip Time Variation, shifted by kRttVarShift - int64_t scaled_rtt_var_ = 0; + // Smoothed Round-Trip Time. + webrtc::TimeDelta srtt_; + // Round-Trip Time Variation. + webrtc::TimeDelta rtt_var_ = webrtc::TimeDelta::Zero(); // Retransmission Timeout - int64_t rto_; + webrtc::TimeDelta rto_; }; } // namespace dcsctp diff --git a/net/dcsctp/tx/retransmission_timeout_test.cc b/net/dcsctp/tx/retransmission_timeout_test.cc index 7754578f32..8686fbe012 100644 --- a/net/dcsctp/tx/retransmission_timeout_test.cc +++ b/net/dcsctp/tx/retransmission_timeout_test.cc @@ -49,10 +49,10 @@ TEST(RetransmissionTimeoutTest, NegativeValuesDoNotAffectRTO) { rto_.ObserveRTT(TimeDelta::Millis(-10)); EXPECT_EQ(rto_.rto(), kInitialRto); rto_.ObserveRTT(TimeDelta::Millis(124)); - EXPECT_EQ(rto_.rto(), TimeDelta::Millis(372)); + EXPECT_EQ(rto_.rto().ms(), 372); // Subsequent negative value rto_.ObserveRTT(TimeDelta::Millis(-10)); - EXPECT_EQ(rto_.rto(), TimeDelta::Millis(372)); + EXPECT_EQ(rto_.rto().ms(), 372); } TEST(RetransmissionTimeoutTest, TooLargeValuesDoNotAffectRTO) { @@ -61,10 +61,10 @@ TEST(RetransmissionTimeoutTest, TooLargeValuesDoNotAffectRTO) { rto_.ObserveRTT(kMaxRtt + TimeDelta::Millis(100)); EXPECT_EQ(rto_.rto(), kInitialRto); rto_.ObserveRTT(TimeDelta::Millis(124)); - EXPECT_EQ(rto_.rto(), TimeDelta::Millis(372)); + EXPECT_EQ(rto_.rto().ms(), 372); // Subsequent too large value rto_.ObserveRTT(kMaxRtt + TimeDelta::Millis(100)); - EXPECT_EQ(rto_.rto(), TimeDelta::Millis(372)); + EXPECT_EQ(rto_.rto().ms(), 372); } TEST(RetransmissionTimeoutTest, WillNeverGoBelowMinimumRto) { @@ -88,29 +88,29 @@ TEST(RetransmissionTimeoutTest, WillNeverGoAboveMaximumRto) { TEST(RetransmissionTimeoutTest, CalculatesRtoForStableRtt) { RetransmissionTimeout rto_(MakeOptions()); rto_.ObserveRTT(TimeDelta::Millis(124)); - EXPECT_EQ(rto_.rto(), TimeDelta::Millis(372)); + EXPECT_EQ(rto_.rto().ms(), 372); rto_.ObserveRTT(TimeDelta::Millis(128)); - EXPECT_EQ(rto_.rto(), TimeDelta::Millis(344)); + EXPECT_EQ(rto_.rto().ms(), 315); rto_.ObserveRTT(TimeDelta::Millis(123)); - EXPECT_EQ(rto_.rto(), TimeDelta::Millis(344)); + EXPECT_EQ(rto_.rto().ms(), 268); rto_.ObserveRTT(TimeDelta::Millis(125)); - EXPECT_EQ(rto_.rto(), TimeDelta::Millis(344)); + EXPECT_EQ(rto_.rto().ms(), 234); rto_.ObserveRTT(TimeDelta::Millis(127)); - EXPECT_EQ(rto_.rto(), TimeDelta::Millis(344)); + EXPECT_EQ(rto_.rto().ms(), 235); } TEST(RetransmissionTimeoutTest, CalculatesRtoForUnstableRtt) { RetransmissionTimeout rto_(MakeOptions()); rto_.ObserveRTT(TimeDelta::Millis(124)); - EXPECT_EQ(rto_.rto(), TimeDelta::Millis(372)); + EXPECT_EQ(rto_.rto().ms(), 372); rto_.ObserveRTT(TimeDelta::Millis(402)); - EXPECT_EQ(rto_.rto(), TimeDelta::Millis(622)); + EXPECT_EQ(rto_.rto().ms(), 623); rto_.ObserveRTT(TimeDelta::Millis(728)); - EXPECT_EQ(rto_.rto(), TimeDelta::Millis(800)); + EXPECT_EQ(rto_.rto().ms(), 800); rto_.ObserveRTT(TimeDelta::Millis(89)); - EXPECT_EQ(rto_.rto(), TimeDelta::Millis(800)); + EXPECT_EQ(rto_.rto().ms(), 800); rto_.ObserveRTT(TimeDelta::Millis(126)); - EXPECT_EQ(rto_.rto(), TimeDelta::Millis(800)); + EXPECT_EQ(rto_.rto().ms(), 800); } TEST(RetransmissionTimeoutTest, WillStabilizeAfterAWhile) { @@ -120,25 +120,25 @@ TEST(RetransmissionTimeoutTest, WillStabilizeAfterAWhile) { rto_.ObserveRTT(TimeDelta::Millis(728)); rto_.ObserveRTT(TimeDelta::Millis(89)); rto_.ObserveRTT(TimeDelta::Millis(126)); - EXPECT_EQ(rto_.rto(), TimeDelta::Millis(800)); + EXPECT_EQ(rto_.rto().ms(), 800); rto_.ObserveRTT(TimeDelta::Millis(124)); - EXPECT_EQ(rto_.rto(), TimeDelta::Millis(800)); + EXPECT_EQ(rto_.rto().ms(), 800); rto_.ObserveRTT(TimeDelta::Millis(122)); - EXPECT_EQ(rto_.rto(), TimeDelta::Millis(710)); + EXPECT_EQ(rto_.rto().ms(), 709); rto_.ObserveRTT(TimeDelta::Millis(123)); - EXPECT_EQ(rto_.rto(), TimeDelta::Millis(631)); + EXPECT_EQ(rto_.rto().ms(), 630); rto_.ObserveRTT(TimeDelta::Millis(124)); - EXPECT_EQ(rto_.rto(), TimeDelta::Millis(562)); + EXPECT_EQ(rto_.rto().ms(), 562); rto_.ObserveRTT(TimeDelta::Millis(122)); - EXPECT_EQ(rto_.rto(), TimeDelta::Millis(505)); + EXPECT_EQ(rto_.rto().ms(), 505); rto_.ObserveRTT(TimeDelta::Millis(124)); - EXPECT_EQ(rto_.rto(), TimeDelta::Millis(454)); + EXPECT_EQ(rto_.rto().ms(), 454); rto_.ObserveRTT(TimeDelta::Millis(124)); - EXPECT_EQ(rto_.rto(), TimeDelta::Millis(410)); + EXPECT_EQ(rto_.rto().ms(), 410); rto_.ObserveRTT(TimeDelta::Millis(124)); - EXPECT_EQ(rto_.rto(), TimeDelta::Millis(372)); + EXPECT_EQ(rto_.rto().ms(), 372); rto_.ObserveRTT(TimeDelta::Millis(124)); - EXPECT_EQ(rto_.rto(), TimeDelta::Millis(367)); + EXPECT_EQ(rto_.rto().ms(), 340); } TEST(RetransmissionTimeoutTest, WillAlwaysStayAboveRTT) { @@ -152,7 +152,7 @@ TEST(RetransmissionTimeoutTest, WillAlwaysStayAboveRTT) { for (int i = 0; i < 1000; ++i) { rto_.ObserveRTT(TimeDelta::Millis(124)); } - EXPECT_EQ(rto_.rto(), TimeDelta::Millis(344)); + EXPECT_EQ(rto_.rto().ms(), 234); } TEST(RetransmissionTimeoutTest, CanSpecifySmallerMinimumRttVariance) { @@ -164,7 +164,7 @@ TEST(RetransmissionTimeoutTest, CanSpecifySmallerMinimumRttVariance) { for (int i = 0; i < 1000; ++i) { rto_.ObserveRTT(TimeDelta::Millis(124)); } - EXPECT_EQ(rto_.rto(), TimeDelta::Millis(244)); + EXPECT_EQ(rto_.rto().ms(), 184); } TEST(RetransmissionTimeoutTest, CanSpecifyLargerMinimumRttVariance) { @@ -176,7 +176,7 @@ TEST(RetransmissionTimeoutTest, CanSpecifyLargerMinimumRttVariance) { for (int i = 0; i < 1000; ++i) { rto_.ObserveRTT(TimeDelta::Millis(124)); } - EXPECT_EQ(rto_.rto(), TimeDelta::Millis(444)); + EXPECT_EQ(rto_.rto().ms(), 284); } } // namespace