Change initial DTLS retransmission timer from 1 second to 50ms.
This will help ensure a timely DTLS handshake when there's packet
loss. It will likely result in spurious retransmissions (since the
RTT is usually > 50ms), but since exponential backoff is still used,
there will at most be ~4 extra retransmissions. For a time-sensitive
application like WebRTC this seems like a reasonable tradeoff.
R=pthatcher@webrtc.org, juberti@chromium.org, juberti@webrtc.org
Review URL: https://codereview.webrtc.org/1981463002 .
Committed: https://crrev.com/1e435628366fb9fed71632369f05928ed857d8ef
Cr-Original-Commit-Position: refs/heads/master@{#12853}
Cr-Commit-Position: refs/heads/master@{#13159}
diff --git a/webrtc/base/gunit.h b/webrtc/base/gunit.h
index 5ba0bd9..ad52a43 100644
--- a/webrtc/base/gunit.h
+++ b/webrtc/base/gunit.h
@@ -21,25 +21,25 @@
#endif
// Wait until "ex" is true, or "timeout" expires.
-#define WAIT(ex, timeout) \
- for (int64_t start = rtc::TimeMillis(); \
- !(ex) && rtc::TimeMillis() < start + timeout;) { \
- rtc::Thread::Current()->ProcessMessages(0); \
- rtc::Thread::Current()->SleepMs(1); \
+#define WAIT(ex, timeout) \
+ for (int64_t start = rtc::SystemTimeMillis(); \
+ !(ex) && rtc::SystemTimeMillis() < start + timeout;) { \
+ rtc::Thread::Current()->ProcessMessages(0); \
+ rtc::Thread::Current()->SleepMs(1); \
}
// This returns the result of the test in res, so that we don't re-evaluate
// the expression in the XXXX_WAIT macros below, since that causes problems
// when the expression is only true the first time you check it.
-#define WAIT_(ex, timeout, res) \
- do { \
- int64_t start = rtc::TimeMillis(); \
- res = (ex); \
- while (!res && rtc::TimeMillis() < start + timeout) { \
- rtc::Thread::Current()->ProcessMessages(0); \
- rtc::Thread::Current()->SleepMs(1); \
- res = (ex); \
- } \
+#define WAIT_(ex, timeout, res) \
+ do { \
+ int64_t start = rtc::SystemTimeMillis(); \
+ res = (ex); \
+ while (!res && rtc::SystemTimeMillis() < start + timeout) { \
+ rtc::Thread::Current()->ProcessMessages(0); \
+ rtc::Thread::Current()->SleepMs(1); \
+ res = (ex); \
+ } \
} while (0)
// The typical EXPECT_XXXX and ASSERT_XXXXs, but done until true or a timeout.
diff --git a/webrtc/base/logging.cc b/webrtc/base/logging.cc
index 6060362..c951e15 100644
--- a/webrtc/base/logging.cc
+++ b/webrtc/base/logging.cc
@@ -124,7 +124,9 @@
const char* module)
: severity_(sev), tag_(kLibjingle) {
if (timestamp_) {
- int64_t time = TimeSince(LogStartTime());
+ // Use SystemTimeMillis so that even if tests use fake clocks, the timestamp
+ // in log messages represents the real system time.
+ int64_t time = TimeDiff(SystemTimeMillis(), LogStartTime());
// Also ensure WallClockStartTime is initialized, so that it matches
// LogStartTime.
WallClockStartTime();
@@ -210,7 +212,7 @@
}
int64_t LogMessage::LogStartTime() {
- static const int64_t g_start = TimeMillis();
+ static const int64_t g_start = SystemTimeMillis();
return g_start;
}
diff --git a/webrtc/base/opensslstreamadapter.cc b/webrtc/base/opensslstreamadapter.cc
index 1d2d03a..fa558fa 100644
--- a/webrtc/base/opensslstreamadapter.cc
+++ b/webrtc/base/opensslstreamadapter.cc
@@ -34,6 +34,7 @@
#include "webrtc/base/openssldigest.h"
#include "webrtc/base/opensslidentity.h"
#include "webrtc/base/stringutils.h"
+#include "webrtc/base/timeutils.h"
#include "webrtc/base/thread.h"
namespace rtc {
@@ -58,7 +59,13 @@
{nullptr, 0}};
#endif
-#ifndef OPENSSL_IS_BORINGSSL
+#ifdef OPENSSL_IS_BORINGSSL
+static void TimeCallback(const SSL* ssl, struct timeval* out_clock) {
+ uint64_t time = TimeNanos();
+ out_clock->tv_sec = time / kNumNanosecsPerSec;
+ out_clock->tv_usec = time / kNumNanosecsPerMicrosec;
+}
+#else // #ifdef OPENSSL_IS_BORINGSSL
// Cipher name table. Maps internal OpenSSL cipher ids to the RFC name.
struct SslCipherMapEntry {
@@ -771,13 +778,19 @@
SSL_set_app_data(ssl_, this);
SSL_set_bio(ssl_, bio, bio); // the SSL object owns the bio now.
-#ifndef OPENSSL_IS_BORINGSSL
if (ssl_mode_ == SSL_MODE_DTLS) {
+#ifdef OPENSSL_IS_BORINGSSL
+ // Change the initial retransmission timer from 1 second to 50ms.
+ // This will likely result in some spurious retransmissions, but
+ // it's useful for ensuring a timely handshake when there's packet
+ // loss.
+ DTLSv1_set_initial_timeout_duration(ssl_, 50);
+#else
// Enable read-ahead for DTLS so whole packets are read from internal BIO
// before parsing. This is done internally by BoringSSL for DTLS.
SSL_set_read_ahead(ssl_, 1);
- }
#endif
+ }
SSL_set_mode(ssl_, SSL_MODE_ENABLE_PARTIAL_WRITE |
SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
@@ -985,6 +998,11 @@
DTLS1_2_VERSION : TLS1_2_VERSION);
break;
}
+ // Set a time callback for BoringSSL because:
+ // 1. Our time function is more accurate (doesn't just use gettimeofday).
+ // 2. This allows us to inject a fake clock for testing.
+ // SSL_CTX_set_current_time_cb(ctx, &TimeCallback);
+ ctx->current_time_cb = &TimeCallback;
#endif
if (identity_ && !identity_->ConfigureIdentity(ctx)) {
@@ -1127,6 +1145,14 @@
#endif
}
+bool OpenSSLStreamAdapter::IsBoringSsl() {
+#ifdef OPENSSL_IS_BORINGSSL
+ return true;
+#else
+ return false;
+#endif
+}
+
#define CDEF(X) \
{ static_cast<uint16_t>(TLS1_CK_##X & 0xffff), "TLS_" #X }
diff --git a/webrtc/base/opensslstreamadapter.h b/webrtc/base/opensslstreamadapter.h
index 1e90bac..05e8102 100644
--- a/webrtc/base/opensslstreamadapter.h
+++ b/webrtc/base/opensslstreamadapter.h
@@ -111,6 +111,7 @@
static bool HaveDtls();
static bool HaveDtlsSrtp();
static bool HaveExporter();
+ static bool IsBoringSsl();
static bool IsAcceptableCipher(int cipher, KeyType key_type);
static bool IsAcceptableCipher(const std::string& cipher, KeyType key_type);
diff --git a/webrtc/base/sslstreamadapter.cc b/webrtc/base/sslstreamadapter.cc
index e0fce3a..44158d4 100644
--- a/webrtc/base/sslstreamadapter.cc
+++ b/webrtc/base/sslstreamadapter.cc
@@ -82,6 +82,9 @@
bool SSLStreamAdapter::HaveExporter() {
return OpenSSLStreamAdapter::HaveExporter();
}
+bool SSLStreamAdapter::IsBoringSsl() {
+ return OpenSSLStreamAdapter::IsBoringSsl();
+}
bool SSLStreamAdapter::IsAcceptableCipher(int cipher, KeyType key_type) {
return OpenSSLStreamAdapter::IsAcceptableCipher(cipher, key_type);
}
diff --git a/webrtc/base/sslstreamadapter.h b/webrtc/base/sslstreamadapter.h
index c5045f1..ba60ce3 100644
--- a/webrtc/base/sslstreamadapter.h
+++ b/webrtc/base/sslstreamadapter.h
@@ -195,6 +195,7 @@
static bool HaveDtls();
static bool HaveDtlsSrtp();
static bool HaveExporter();
+ static bool IsBoringSsl();
// Returns true iff the supplied cipher is deemed to be strong.
// TODO(torbjorng): Consider removing the KeyType argument.
diff --git a/webrtc/base/timeutils.cc b/webrtc/base/timeutils.cc
index 3c89d80..1be368e 100644
--- a/webrtc/base/timeutils.cc
+++ b/webrtc/base/timeutils.cc
@@ -38,10 +38,7 @@
return prev;
}
-uint64_t TimeNanos() {
- if (g_clock) {
- return g_clock->TimeNanos();
- }
+uint64_t SystemTimeNanos() {
int64_t ticks;
#if defined(WEBRTC_MAC)
static mach_timebase_info_data_t timebase;
@@ -86,6 +83,17 @@
return ticks;
}
+int64_t SystemTimeMillis() {
+ return static_cast<int64_t>(SystemTimeNanos() / kNumNanosecsPerMillisec);
+}
+
+uint64_t TimeNanos() {
+ if (g_clock) {
+ return g_clock->TimeNanos();
+ }
+ return SystemTimeNanos();
+}
+
uint32_t Time32() {
return static_cast<uint32_t>(TimeNanos() / kNumNanosecsPerMillisec);
}
diff --git a/webrtc/base/timeutils.h b/webrtc/base/timeutils.h
index 113793a..25ef521 100644
--- a/webrtc/base/timeutils.h
+++ b/webrtc/base/timeutils.h
@@ -53,6 +53,11 @@
// that uses it, eliminating the need for a global variable and this function.
ClockInterface* SetClockForTesting(ClockInterface* clock);
+// Returns the actual system time, even if a clock is set for testing.
+// Useful for timeouts while using a test clock, or for logging.
+uint64_t SystemTimeNanos();
+int64_t SystemTimeMillis();
+
// Returns the current time in milliseconds in 32 bits.
uint32_t Time32();
diff --git a/webrtc/p2p/base/dtlstransportchannel_unittest.cc b/webrtc/p2p/base/dtlstransportchannel_unittest.cc
index 486b51a..705df2d 100644
--- a/webrtc/p2p/base/dtlstransportchannel_unittest.cc
+++ b/webrtc/p2p/base/dtlstransportchannel_unittest.cc
@@ -22,10 +22,10 @@
#include "webrtc/base/sslstreamadapter.h"
#include "webrtc/base/stringutils.h"
-#define MAYBE_SKIP_TEST(feature) \
- if (!(rtc::SSLStreamAdapter::feature())) { \
- LOG(LS_INFO) << "Feature disabled... skipping"; \
- return; \
+#define MAYBE_SKIP_TEST(feature) \
+ if (!(rtc::SSLStreamAdapter::feature())) { \
+ LOG(LS_INFO) << #feature " feature disabled... skipping"; \
+ return; \
}
static const char kIceUfrag1[] = "TESTICEUFRAG0001";
@@ -413,7 +413,8 @@
rtc::SentPacket sent_packet_;
};
-
+// Note that this test always uses a FakeClock, due to the |fake_clock_| member
+// variable.
class DtlsTransportChannelTest : public testing::Test {
public:
DtlsTransportChannelTest()
@@ -561,6 +562,7 @@
}
protected:
+ rtc::ScopedFakeClock fake_clock_;
DtlsTestClient client1_;
DtlsTestClient client2_;
int channel_ct_;
@@ -974,3 +976,45 @@
kTimeout);
EXPECT_EQ(1, client1_.received_dtls_client_hellos());
}
+
+// Test that packets are retransmitted according to the expected schedule.
+// Each time a timeout occurs, the retransmission timer should be doubled up to
+// 60 seconds. The timer defaults to 1 second, but for WebRTC we should be
+// initializing it to 50ms.
+TEST_F(DtlsTransportChannelTest, TestRetransmissionSchedule) {
+ MAYBE_SKIP_TEST(HaveDtls);
+ // We can only change the retransmission schedule with a recently-added
+ // BoringSSL API. Skip the test if not built with BoringSSL.
+ MAYBE_SKIP_TEST(IsBoringSsl);
+
+ PrepareDtls(true, true, rtc::KT_DEFAULT);
+ // Exchange transport descriptions.
+ Negotiate(cricket::CONNECTIONROLE_ACTPASS, cricket::CONNECTIONROLE_ACTIVE);
+
+ // Make client2_ writable, but not client1_.
+ // This means client1_ will send DTLS client hellos but get no response.
+ EXPECT_TRUE(client2_.Connect(&client1_, true));
+ EXPECT_TRUE_WAIT(client2_.all_raw_channels_writable(), kTimeout);
+
+ // Wait for the first client hello to be sent.
+ EXPECT_EQ_WAIT(1, client1_.received_dtls_client_hellos(), kTimeout);
+ EXPECT_FALSE(client1_.all_raw_channels_writable());
+
+ static int timeout_schedule_ms[] = {50, 100, 200, 400, 800, 1600,
+ 3200, 6400, 12800, 25600, 51200, 60000};
+
+ int expected_hellos = 1;
+ for (size_t i = 0;
+ i < (sizeof(timeout_schedule_ms) / sizeof(timeout_schedule_ms[0]));
+ ++i) {
+ // For each expected retransmission time, advance the fake clock a
+ // millisecond before the expected time and verify that no unexpected
+ // retransmissions were sent. Then advance it the final millisecond and
+ // verify that the expected retransmission was sent.
+ fake_clock_.AdvanceTime(
+ rtc::TimeDelta::FromMilliseconds(timeout_schedule_ms[i] - 1));
+ EXPECT_EQ(expected_hellos, client1_.received_dtls_client_hellos());
+ fake_clock_.AdvanceTime(rtc::TimeDelta::FromMilliseconds(1));
+ EXPECT_EQ(++expected_hellos, client1_.received_dtls_client_hellos());
+ }
+}