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:
parent
e249d195e0
commit
27e50ccf4c
@ -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);
|
||||
|
||||
|
||||
@ -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",
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
64
net/dcsctp/tx/retransmission_timeout.cc
Normal file
64
net/dcsctp/tx/retransmission_timeout.cc
Normal 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
|
||||
58
net/dcsctp/tx/retransmission_timeout.h
Normal file
58
net/dcsctp/tx/retransmission_timeout.h
Normal 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_
|
||||
136
net/dcsctp/tx/retransmission_timeout_test.cc
Normal file
136
net/dcsctp/tx/retransmission_timeout_test.cc
Normal 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
|
||||
Loading…
x
Reference in New Issue
Block a user