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 <boivie@webrtc.org>
Reviewed-by: Harald Alvestrand <hta@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#33832}
This commit is contained in:
Victor Boivie 2021-04-05 08:28:42 +02:00 committed by Commit Bot
parent e249d195e0
commit 27e50ccf4c
5 changed files with 277 additions and 0 deletions

View File

@ -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);

View File

@ -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",
]
}
}

View File

@ -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 <cmath>
#include <cstdint>
#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

View File

@ -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 <cstdint>
#include <functional>
#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_

View File

@ -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